@lodashventure/medusa-parcel-shipping 0.2.11 → 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 +1794 -1289
  2. package/.medusa/server/src/admin/index.mjs +1797 -1292
  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,1542 +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
4
  const ui = require("@medusajs/ui");
7
- const requestJson$1 = async (url, method = "GET", body) => {
8
- const response = await fetch(url, {
9
- method,
10
- headers: {
11
- "Content-Type": "application/json"
12
- },
13
- body: body ? JSON.stringify(body) : void 0,
14
- credentials: "include"
15
- });
16
- if (!response.ok) {
17
- let message = response.statusText;
18
- try {
19
- const payload = await response.json();
20
- message = (payload == null ? void 0 : payload.code) ?? (payload == null ? void 0 : payload.error) ?? message;
21
- } catch (_) {
22
- }
23
- throw new Error(message);
24
- }
25
- if (response.status === 204) {
26
- return void 0;
27
- }
28
- return await response.json();
29
- };
30
- const toNumber$1 = (value) => {
31
- if (value.trim().length === 0) {
32
- return null;
33
- }
34
- const parsed = Number(value);
35
- return Number.isFinite(parsed) ? parsed : null;
36
- };
37
- function ParcelBoxesPage() {
38
- const [boxes, setBoxes] = react.useState([]);
39
- const [loading, setLoading] = react.useState(true);
40
- const [formState, setFormState] = react.useState({
41
- id: null,
42
- name: "",
43
- width_cm: "",
44
- length_cm: "",
45
- height_cm: "",
46
- max_weight_kg: "",
47
- price_thb: "",
48
- active: true
49
- });
50
- const [saving, setSaving] = react.useState(false);
51
- const [deletingId, setDeletingId] = react.useState(null);
52
- 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 () => {
53
14
  setLoading(true);
15
+ setError(null);
54
16
  try {
55
- const data = await requestJson$1(
56
- "/admin/parcel-boxes"
57
- );
58
- setBoxes(data.boxes ?? []);
59
- } catch (error) {
60
- ui.toast.error(error.message ?? "Failed to load boxes", {
61
- dismissable: true
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
+ };
32
+ });
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)
62
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);
63
62
  } finally {
64
63
  setLoading(false);
65
64
  }
66
- }, []);
67
- react.useEffect(() => {
68
- refresh();
69
- }, [refresh]);
70
- const resetForm = () => setFormState({
71
- id: null,
72
- name: "",
73
- width_cm: "",
74
- length_cm: "",
75
- height_cm: "",
76
- max_weight_kg: "",
77
- 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: "",
78
301
  active: true
79
302
  });
80
- const handleSubmit = async () => {
81
- const width = toNumber$1(formState.width_cm);
82
- const length = toNumber$1(formState.length_cm);
83
- const height = toNumber$1(formState.height_cm);
84
- const maxWeight = toNumber$1(formState.max_weight_kg);
85
- const price = toNumber$1(formState.price_thb);
86
- if (!formState.name.trim() || width === null || length === null || height === null || maxWeight === null || price === null) {
87
- ui.toast.error("Fill all fields with valid numeric values", {
88
- 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(", ")
89
332
  });
90
333
  return;
91
334
  }
92
- const payload = {
93
- name: formState.name.trim(),
94
- width_cm: width,
95
- length_cm: length,
96
- height_cm: height,
97
- max_weight_kg: maxWeight,
98
- price_thb: price,
99
- active: formState.active
100
- };
101
- setSaving(true);
335
+ setIsSaving(true);
102
336
  try {
103
- if (formState.id) {
104
- await requestJson$1(
105
- `/admin/parcel-boxes/${formState.id}`,
106
- "PUT",
107
- payload
108
- );
109
- ui.toast.success("Box updated", { dismissable: true });
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 ? "แก้ไขพื้นที่บริการแล้ว" : "สร้างพื้นที่บริการใหม่แล้ว"
353
+ });
354
+ onClose();
110
355
  } else {
111
- await requestJson$1("/admin/parcel-boxes", "POST", payload);
112
- ui.toast.success("Box created", { dismissable: true });
356
+ const error = await response.json();
357
+ throw new Error(error.message || "Failed to save");
113
358
  }
114
- await refresh();
115
- resetForm();
116
359
  } catch (error) {
117
- ui.toast.error(error.message ?? "Failed to save box", {
118
- dismissable: true
360
+ ui.toast.error("ข้อผิดพลาด", {
361
+ description: error instanceof Error ? error.message : "ไม่สามารถบันทึกข้อมูลได้"
119
362
  });
120
363
  } finally {
121
- setSaving(false);
364
+ setIsSaving(false);
122
365
  }
123
366
  };
124
- const handleDelete = async (id) => {
125
- 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);
126
475
  try {
127
- await requestJson$1(`/admin/parcel-boxes/${id}`, "DELETE");
128
- ui.toast.success("Box removed", { dismissable: true });
129
- await refresh();
476
+ const response = await fetch("/admin/service-areas", {
477
+ credentials: "include"
478
+ });
479
+ const data = await response.json();
480
+ console.log("Fetched service areas:", data);
481
+ setAreas(data.service_areas || []);
130
482
  } catch (error) {
131
- ui.toast.error(error.message ?? "Failed to delete box", {
132
- dismissable: true
483
+ ui.toast.error("ข้อผิดพลาด", {
484
+ description: "ไม่สามารถโหลดข้อมูลพื้นที่บริการได้"
133
485
  });
134
486
  } finally {
135
- setDeletingId(null);
487
+ setIsLoading(false);
136
488
  }
137
489
  };
138
- const handleToggle = async (box) => {
490
+ const handleToggleActive = async (area) => {
139
491
  try {
140
- await requestJson$1(`/admin/parcel-boxes/${box.id}`, "PUT", {
141
- active: !box.active
492
+ const response = await fetch(`/admin/service-areas/${area.id}`, {
493
+ method: "PUT",
494
+ headers: { "Content-Type": "application/json" },
495
+ credentials: "include",
496
+ body: JSON.stringify({ ...area, active: !area.active })
142
497
  });
143
- 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
+ }
144
506
  } catch (error) {
145
- ui.toast.error(error.message ?? "Failed to update box status", {
146
- dismissable: true
507
+ ui.toast.error("ข้อผิดพลาด", {
508
+ description: "ไม่สามารถอัพเดทสถานะได้"
147
509
  });
148
510
  }
149
511
  };
150
- const tableRows = react.useMemo(() => {
151
- if (loading) {
152
- 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..." }) }) });
153
- }
154
- if (boxes.length === 0) {
155
- 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("");
156
534
  }
157
- return boxes.map((box) => /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
158
- /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "font-medium", children: box.name }) }),
159
- /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "text-sm text-ui-fg-subtle", children: [
160
- box.width_cm,
161
- " × ",
162
- box.length_cm,
163
- " × ",
164
- box.height_cm
165
- ] }) }),
166
- /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: box.max_weight_kg }),
167
- /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: box.price_thb }),
168
- /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { color: box.active ? "green" : "grey", children: box.active ? "Active" : "Inactive" }) }),
169
- /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-end gap-2", children: [
170
- /* @__PURE__ */ jsxRuntime.jsx(
171
- ui.Switch,
172
- {
173
- checked: box.active,
174
- onCheckedChange: () => handleToggle(box)
175
- }
176
- ),
177
- /* @__PURE__ */ jsxRuntime.jsx(
178
- ui.Button,
179
- {
180
- variant: "secondary",
181
- size: "small",
182
- onClick: () => setFormState({
183
- id: box.id,
184
- name: box.name,
185
- width_cm: String(box.width_cm),
186
- length_cm: String(box.length_cm),
187
- height_cm: String(box.height_cm),
188
- max_weight_kg: String(box.max_weight_kg),
189
- price_thb: String(box.price_thb),
190
- active: box.active
191
- }),
192
- children: "Edit"
193
- }
194
- ),
195
- /* @__PURE__ */ jsxRuntime.jsx(
196
- ui.Button,
197
- {
198
- variant: "danger",
199
- size: "small",
200
- onClick: () => handleDelete(box.id),
201
- disabled: deletingId === box.id,
202
- children: "Delete"
203
- }
204
- )
205
- ] }) })
206
- ] }, box.id));
207
- }, [boxes, deletingId, loading]);
208
- return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "space-y-6 p-6", children: [
209
- /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
210
- /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h1", children: "Parcel Boxes" }),
211
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: "Configure and manage parcel box sizes and pricing." })
212
- ] }),
213
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-4 rounded-md border p-4", children: [
214
- /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: formState.id ? "Edit Box" : "Create Box" }),
215
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-4 md:grid-cols-3", children: [
216
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
217
- /* @__PURE__ */ jsxRuntime.jsx(
218
- ui.Label,
219
- {
220
- className: "text-sm font-medium text-ui-fg-base",
221
- htmlFor: "parcel-box-name",
222
- children: "Name"
223
- }
224
- ),
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: [
225
557
  /* @__PURE__ */ jsxRuntime.jsx(
226
558
  ui.Input,
227
559
  {
228
- id: "parcel-box-name",
229
- placeholder: "Enter box name",
230
- value: formState.name,
231
- onChange: (event) => setFormState((prev) => ({ ...prev, name: event.target.value }))
232
- }
233
- )
234
- ] }),
235
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
236
- /* @__PURE__ */ jsxRuntime.jsx(
237
- ui.Label,
238
- {
239
- className: "text-sm font-medium text-ui-fg-base",
240
- htmlFor: "parcel-box-width",
241
- children: "Width (cm)"
560
+ type: "search",
561
+ placeholder: "ค้นหาจังหวัดหรือรหัสไปรษณีย์...",
562
+ value: searchTerm,
563
+ onChange: (e) => setSearchTerm(e.target.value),
564
+ className: "max-w-md"
242
565
  }
243
566
  ),
244
- /* @__PURE__ */ jsxRuntime.jsx(
245
- ui.Input,
246
- {
247
- id: "parcel-box-width",
248
- placeholder: "e.g. 20",
249
- type: "number",
250
- value: formState.width_cm,
251
- onChange: (event) => setFormState((prev) => ({
252
- ...prev,
253
- width_cm: event.target.value
254
- }))
255
- }
256
- )
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
+ ] })
257
571
  ] }),
258
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
259
- /* @__PURE__ */ jsxRuntime.jsx(
260
- ui.Label,
261
- {
262
- className: "text-sm font-medium text-ui-fg-base",
263
- htmlFor: "parcel-box-length",
264
- children: "Length (cm)"
265
- }
266
- ),
267
- /* @__PURE__ */ jsxRuntime.jsx(
268
- ui.Input,
269
- {
270
- id: "parcel-box-length",
271
- placeholder: "e.g. 30",
272
- type: "number",
273
- value: formState.length_cm,
274
- onChange: (event) => setFormState((prev) => ({
275
- ...prev,
276
- length_cm: event.target.value
277
- }))
278
- }
279
- )
280
- ] }),
281
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
282
- /* @__PURE__ */ jsxRuntime.jsx(
283
- 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,
284
589
  {
285
- className: "text-sm font-medium text-ui-fg-base",
286
- htmlFor: "parcel-box-height",
287
- children: "Height (cm)"
590
+ color: area.active ? "green" : "grey",
591
+ className: "cursor-pointer",
592
+ onClick: () => handleToggleActive(area),
593
+ children: area.active ? "เปิดใช้งาน" : "ปิดใช้งาน"
288
594
  }
289
- ),
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) *" }),
290
768
  /* @__PURE__ */ jsxRuntime.jsx(
291
769
  ui.Input,
292
770
  {
293
- id: "parcel-box-height",
294
- placeholder: "e.g. 15",
771
+ id: "width",
295
772
  type: "number",
296
- value: formState.height_cm,
297
- onChange: (event) => setFormState((prev) => ({
298
- ...prev,
299
- height_cm: event.target.value
300
- }))
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
301
778
  }
302
779
  )
303
780
  ] }),
304
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
305
- /* @__PURE__ */ jsxRuntime.jsx(
306
- ui.Label,
307
- {
308
- className: "text-sm font-medium text-ui-fg-base",
309
- htmlFor: "parcel-box-max-weight",
310
- children: "Max Weight (kg)"
311
- }
312
- ),
781
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
782
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "length", children: "ความยาว (cm) *" }),
313
783
  /* @__PURE__ */ jsxRuntime.jsx(
314
784
  ui.Input,
315
785
  {
316
- id: "parcel-box-max-weight",
317
- placeholder: "e.g. 0.5",
786
+ id: "length",
318
787
  type: "number",
319
- value: formState.max_weight_kg,
320
- onChange: (event) => setFormState((prev) => ({
321
- ...prev,
322
- max_weight_kg: event.target.value
323
- }))
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
324
793
  }
325
794
  )
326
795
  ] }),
327
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
328
- /* @__PURE__ */ jsxRuntime.jsx(
329
- ui.Label,
330
- {
331
- className: "text-sm font-medium text-ui-fg-base",
332
- htmlFor: "parcel-box-price",
333
- children: "Box Price (THB)"
334
- }
335
- ),
796
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
797
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "height", children: "ความสูง (cm) *" }),
336
798
  /* @__PURE__ */ jsxRuntime.jsx(
337
799
  ui.Input,
338
800
  {
339
- id: "parcel-box-price",
340
- placeholder: "e.g. 10",
801
+ id: "height",
341
802
  type: "number",
342
- value: formState.price_thb,
343
- onChange: (event) => setFormState((prev) => ({
344
- ...prev,
345
- price_thb: event.target.value
346
- }))
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
347
808
  }
348
809
  )
349
810
  ] })
350
811
  ] }),
351
- /* @__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: [
352
843
  /* @__PURE__ */ jsxRuntime.jsx(
353
844
  ui.Switch,
354
845
  {
355
- checked: formState.active,
356
- onCheckedChange: (active) => setFormState((prev) => ({ ...prev, active }))
846
+ id: "active",
847
+ checked: formData.active,
848
+ onCheckedChange: (checked) => setFormData({ ...formData, active: checked })
357
849
  }
358
850
  ),
359
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-sm text-ui-fg-subtle", children: "Active" })
851
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "active", children: "เปิดใช้งาน" })
360
852
  ] }),
361
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-3", children: [
362
- /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { onClick: handleSubmit, disabled: saving, children: formState.id ? "Update" : "Create" }),
363
- 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 ? "กำลังบันทึก..." : "บันทึก" })
364
856
  ] })
365
- ] }),
366
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "overflow-hidden rounded-md border", children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Table, { children: [
367
- /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Header, { children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
368
- /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Name" }),
369
- /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Dimensions (cm)" }),
370
- /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Max Weight (kg)" }),
371
- /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Price" }),
372
- /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Status" }),
373
- /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { className: "text-right", children: "Actions" })
374
- ] }) }),
375
- /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Body, { children: tableRows })
376
857
  ] }) })
377
- ] });
378
- }
379
- const ParcelBoxesRoute = () => {
380
- return /* @__PURE__ */ jsxRuntime.jsx(ParcelBoxesPage, {});
858
+ ] }) });
381
859
  };
382
- const config$1 = adminSdk.defineRouteConfig({
383
- label: "Parcel Boxes",
384
- icon: icons.FlyingBox
385
- });
386
- const requestJson = async (url, method = "GET", body) => {
387
- const response = await fetch(url, {
388
- method,
389
- headers: {
390
- "Content-Type": "application/json"
391
- },
392
- body: body ? JSON.stringify(body) : void 0,
393
- credentials: "include"
394
- });
395
- if (!response.ok) {
396
- let message = response.statusText;
397
- try {
398
- const payload = await response.json();
399
- message = (payload == null ? void 0 : payload.code) ?? (payload == null ? void 0 : payload.error) ?? message;
400
- } catch (_) {
401
- }
402
- throw new Error(message);
403
- }
404
- if (response.status === 204) {
405
- return void 0;
406
- }
407
- return await response.json();
408
- };
409
- const toNumber = (value) => {
410
- if (value.trim().length === 0) {
411
- return null;
412
- }
413
- const parsed = Number(value);
414
- return Number.isFinite(parsed) ? parsed : null;
415
- };
416
- const getThaiAddressComponents = () => null;
417
- function ParcelShippingPage() {
418
- const [tab, setTab] = react.useState("boxes");
419
- return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "space-y-6 p-6", children: [
420
- /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
421
- /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h1", children: "Parcel Shipping" }),
422
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: "Configure parcel boxes, shipping rates, and supported service areas." })
423
- ] }),
424
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
425
- /* @__PURE__ */ jsxRuntime.jsx(
426
- ui.Button,
427
- {
428
- variant: tab === "boxes" ? "primary" : "secondary",
429
- size: "small",
430
- onClick: () => setTab("boxes"),
431
- children: "Boxes"
432
- }
433
- ),
434
- /* @__PURE__ */ jsxRuntime.jsx(
435
- ui.Button,
436
- {
437
- variant: tab === "rates" ? "primary" : "secondary",
438
- size: "small",
439
- onClick: () => setTab("rates"),
440
- children: "Shipping Rates"
441
- }
442
- ),
443
- /* @__PURE__ */ jsxRuntime.jsx(
444
- ui.Button,
445
- {
446
- variant: tab === "areas" ? "primary" : "secondary",
447
- size: "small",
448
- onClick: () => setTab("areas"),
449
- children: "Service Areas"
450
- }
451
- )
452
- ] }),
453
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pt-6", children: [
454
- tab === "boxes" && /* @__PURE__ */ jsxRuntime.jsx(BoxesSection, {}),
455
- tab === "rates" && /* @__PURE__ */ jsxRuntime.jsx(RatesSection, {}),
456
- tab === "areas" && /* @__PURE__ */ jsxRuntime.jsx(AreasSection, {})
457
- ] })
458
- ] });
459
- }
460
- const BoxesSection = () => {
860
+ const ParcelBoxesTable = () => {
461
861
  const [boxes, setBoxes] = react.useState([]);
462
- const [loading, setLoading] = react.useState(true);
463
- const [formState, setFormState] = react.useState({
464
- id: null,
465
- name: "",
466
- width_cm: "",
467
- length_cm: "",
468
- height_cm: "",
469
- max_weight_kg: "",
470
- price_thb: "",
471
- active: true
472
- });
473
- const [saving, setSaving] = react.useState(false);
474
- const [deletingId, setDeletingId] = react.useState(null);
475
- const refresh = react.useCallback(async () => {
476
- 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);
477
880
  try {
478
- const data = await requestJson(
479
- "/admin/parcel-boxes"
480
- );
481
- setBoxes(data.boxes ?? []);
881
+ const response = await fetch("/admin/boxes", {
882
+ credentials: "include"
883
+ });
884
+ const data = await response.json();
885
+ setBoxes(data.boxes || []);
482
886
  } catch (error) {
483
- ui.toast.error(error.message ?? "Failed to load boxes", {
484
- dismissable: true
887
+ ui.toast.error("ข้อผิดพลาด", {
888
+ description: "ไม่สามารถโหลดข้อมูลกล่องพัสดุได้"
485
889
  });
486
890
  } finally {
487
- setLoading(false);
488
- }
489
- }, []);
490
- react.useEffect(() => {
491
- refresh();
492
- }, [refresh]);
493
- const resetForm = () => setFormState({
494
- id: null,
495
- name: "",
496
- width_cm: "",
497
- length_cm: "",
498
- height_cm: "",
499
- max_weight_kg: "",
500
- price_thb: "",
501
- active: true
502
- });
503
- const handleSubmit = async () => {
504
- const width = toNumber(formState.width_cm);
505
- const length = toNumber(formState.length_cm);
506
- const height = toNumber(formState.height_cm);
507
- const maxWeight = toNumber(formState.max_weight_kg);
508
- const price = toNumber(formState.price_thb);
509
- if (!formState.name.trim() || width === null || length === null || height === null || maxWeight === null || price === null) {
510
- ui.toast.error("Fill all fields with valid numeric values", {
511
- dismissable: true
512
- });
513
- return;
891
+ setIsLoading(false);
514
892
  }
515
- const payload = {
516
- name: formState.name.trim(),
517
- width_cm: width,
518
- length_cm: length,
519
- height_cm: height,
520
- max_weight_kg: maxWeight,
521
- price_thb: price,
522
- active: formState.active
523
- };
524
- setSaving(true);
893
+ };
894
+ const handleToggleActive = async (box) => {
525
895
  try {
526
- if (formState.id) {
527
- await requestJson(
528
- `/admin/parcel-boxes/${formState.id}`,
529
- "PUT",
530
- payload
531
- );
532
- ui.toast.success("Box updated", { dismissable: true });
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} แล้ว`
905
+ });
906
+ fetchBoxes();
533
907
  } else {
534
- await requestJson("/admin/parcel-boxes", "POST", payload);
535
- ui.toast.success("Box created", { dismissable: true });
908
+ throw new Error("Failed to update");
536
909
  }
537
- await refresh();
538
- resetForm();
539
910
  } catch (error) {
540
- ui.toast.error(error.message ?? "Failed to save box", {
541
- dismissable: true
911
+ ui.toast.error("ข้อผิดพลาด", {
912
+ description: "ไม่สามารถอัพเดทสถานะได้"
542
913
  });
543
- } finally {
544
- setSaving(false);
545
914
  }
546
915
  };
547
- const handleDelete = async (id) => {
548
- setDeletingId(id);
916
+ const handleDelete = async () => {
917
+ if (!deleteBoxId) return;
549
918
  try {
550
- await requestJson(`/admin/parcel-boxes/${id}`, "DELETE");
551
- ui.toast.success("Box removed", { dismissable: true });
552
- await refresh();
919
+ const response = await fetch(`/admin/boxes/${deleteBoxId}`, {
920
+ method: "DELETE",
921
+ credentials: "include"
922
+ });
923
+ if (response.ok) {
924
+ ui.toast.success("สำเร็จ", {
925
+ description: `ลบกล่อง ${deleteBoxName} แล้ว`
926
+ });
927
+ fetchBoxes();
928
+ } else {
929
+ throw new Error("Failed to delete");
930
+ }
553
931
  } catch (error) {
554
- ui.toast.error(error.message ?? "Failed to delete box", {
555
- dismissable: true
932
+ ui.toast.error("ข้อผิดพลาด", {
933
+ description: "ไม่สามารถลบกล่องได้"
556
934
  });
557
935
  } finally {
558
- setDeletingId(null);
936
+ setDeleteBoxId(null);
937
+ setDeleteBoxName("");
559
938
  }
560
939
  };
561
- 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);
562
1120
  try {
563
- await requestJson(`/admin/parcel-boxes/${box.id}`, "PUT", {
564
- 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)
565
1137
  });
566
- 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
+ }
567
1147
  } catch (error) {
568
- ui.toast.error(error.message ?? "Failed to update box status", {
569
- dismissable: true
1148
+ ui.toast.error("ข้อผิดพลาด", {
1149
+ description: error instanceof Error ? error.message : "ไม่สามารถบันทึกข้อมูลได้"
570
1150
  });
1151
+ } finally {
1152
+ setIsSaving(false);
571
1153
  }
572
1154
  };
573
- const tableRows = react.useMemo(() => {
574
- if (loading) {
575
- 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..." }) }) });
576
- }
577
- if (boxes.length === 0) {
578
- 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." }) }) });
579
- }
580
- return boxes.map((box) => /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
581
- /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "font-medium", children: box.name }) }),
582
- /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "text-sm text-ui-fg-subtle", children: [
583
- box.width_cm,
584
- " × ",
585
- box.length_cm,
586
- " × ",
587
- box.height_cm
588
- ] }) }),
589
- /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: box.max_weight_kg }),
590
- /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: box.price_thb }),
591
- /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { color: box.active ? "green" : "grey", children: box.active ? "Active" : "Inactive" }) }),
592
- /* @__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: "ชื่อวัสดุ *" }),
593
1160
  /* @__PURE__ */ jsxRuntime.jsx(
594
- ui.Switch,
1161
+ ui.Input,
595
1162
  {
596
- checked: box.active,
597
- onCheckedChange: () => handleToggle(box)
1163
+ id: "name",
1164
+ value: formData.name,
1165
+ onChange: (e) => setFormData({ ...formData, name: e.target.value }),
1166
+ placeholder: "เช่น พลาสติกกันกระแทก, กล่องกระดาษ",
1167
+ required: true
598
1168
  }
599
- ),
600
- /* @__PURE__ */ jsxRuntime.jsx(
601
- ui.Button,
602
- {
603
- variant: "secondary",
604
- size: "small",
605
- onClick: () => setFormState({
606
- id: box.id,
607
- name: box.name,
608
- width_cm: String(box.width_cm),
609
- length_cm: String(box.length_cm),
610
- height_cm: String(box.height_cm),
611
- max_weight_kg: String(box.max_weight_kg),
612
- price_thb: String(box.price_thb),
613
- active: box.active
614
- }),
615
- children: "Edit"
616
- }
617
- ),
618
- /* @__PURE__ */ jsxRuntime.jsx(
619
- 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,
620
1175
  {
621
- variant: "danger",
622
- size: "small",
623
- onClick: () => handleDelete(box.id),
624
- disabled: deletingId === box.id,
625
- 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
+ ]
626
1182
  }
627
1183
  )
628
- ] }) })
629
- ] }, box.id));
630
- }, [boxes, deletingId, loading]);
631
- return /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "space-y-6", children: [
632
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-4 rounded-md border p-4", children: [
633
- /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: formState.id ? "Edit Box" : "Create Box" }),
634
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-4 md:grid-cols-3", children: [
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-name",
641
- children: "Name"
642
- }
643
- ),
644
- /* @__PURE__ */ jsxRuntime.jsx(
645
- ui.Input,
646
- {
647
- id: "parcel-box-name",
648
- placeholder: "Enter box name",
649
- value: formState.name,
650
- onChange: (event) => setFormState((prev) => ({ ...prev, name: event.target.value }))
651
- }
652
- )
653
- ] }),
654
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
655
- /* @__PURE__ */ jsxRuntime.jsx(
656
- ui.Label,
657
- {
658
- className: "text-sm font-medium text-ui-fg-base",
659
- htmlFor: "parcel-box-width",
660
- children: "Width (cm)"
661
- }
662
- ),
663
- /* @__PURE__ */ jsxRuntime.jsx(
664
- ui.Input,
665
- {
666
- id: "parcel-box-width",
667
- placeholder: "e.g. 20",
668
- type: "number",
669
- value: formState.width_cm,
670
- onChange: (event) => setFormState((prev) => ({
671
- ...prev,
672
- width_cm: event.target.value
673
- }))
674
- }
675
- )
676
- ] }),
677
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
678
- /* @__PURE__ */ jsxRuntime.jsx(
679
- ui.Label,
680
- {
681
- className: "text-sm font-medium text-ui-fg-base",
682
- htmlFor: "parcel-box-length",
683
- children: "Length (cm)"
684
- }
685
- ),
686
- /* @__PURE__ */ jsxRuntime.jsx(
687
- ui.Input,
688
- {
689
- id: "parcel-box-length",
690
- placeholder: "e.g. 30",
691
- type: "number",
692
- value: formState.length_cm,
693
- onChange: (event) => setFormState((prev) => ({
694
- ...prev,
695
- length_cm: event.target.value
696
- }))
697
- }
698
- )
699
- ] }),
700
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
701
- /* @__PURE__ */ jsxRuntime.jsx(
702
- ui.Label,
703
- {
704
- className: "text-sm font-medium text-ui-fg-base",
705
- htmlFor: "parcel-box-height",
706
- children: "Height (cm)"
707
- }
708
- ),
709
- /* @__PURE__ */ jsxRuntime.jsx(
710
- ui.Input,
711
- {
712
- id: "parcel-box-height",
713
- placeholder: "e.g. 15",
714
- type: "number",
715
- value: formState.height_cm,
716
- onChange: (event) => setFormState((prev) => ({
717
- ...prev,
718
- height_cm: event.target.value
719
- }))
720
- }
721
- )
722
- ] }),
723
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
724
- /* @__PURE__ */ jsxRuntime.jsx(
725
- ui.Label,
726
- {
727
- className: "text-sm font-medium text-ui-fg-base",
728
- htmlFor: "parcel-box-max-weight",
729
- children: "Max Weight (kg)"
730
- }
731
- ),
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: "หน่วย *" }),
732
1188
  /* @__PURE__ */ jsxRuntime.jsx(
733
1189
  ui.Input,
734
1190
  {
735
- id: "parcel-box-max-weight",
736
- placeholder: "e.g. 0.5",
737
- type: "number",
738
- value: formState.max_weight_kg,
739
- onChange: (event) => setFormState((prev) => ({
740
- ...prev,
741
- max_weight_kg: event.target.value
742
- }))
1191
+ id: "unit",
1192
+ value: formData.unit,
1193
+ onChange: (e) => setFormData({ ...formData, unit: e.target.value }),
1194
+ placeholder: "เช่น ชิ้น, เมตร, กิโลกรัม",
1195
+ required: true
743
1196
  }
744
1197
  )
745
1198
  ] }),
746
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
747
- /* @__PURE__ */ jsxRuntime.jsx(
748
- ui.Label,
749
- {
750
- className: "text-sm font-medium text-ui-fg-base",
751
- htmlFor: "parcel-box-price",
752
- children: "Box Price (THB)"
753
- }
754
- ),
1199
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1200
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "costPerUnit", children: "ต้นทุนต่อหน่วย *" }),
755
1201
  /* @__PURE__ */ jsxRuntime.jsx(
756
1202
  ui.Input,
757
1203
  {
758
- id: "parcel-box-price",
759
- placeholder: "e.g. 10",
1204
+ id: "costPerUnit",
760
1205
  type: "number",
761
- value: formState.price_thb,
762
- onChange: (event) => setFormState((prev) => ({
763
- ...prev,
764
- price_thb: event.target.value
765
- }))
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
766
1211
  }
767
1212
  )
768
1213
  ] })
769
1214
  ] }),
770
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
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: [
771
1241
  /* @__PURE__ */ jsxRuntime.jsx(
772
1242
  ui.Switch,
773
1243
  {
774
- checked: formState.active,
775
- onCheckedChange: (active) => setFormState((prev) => ({ ...prev, active }))
1244
+ id: "active",
1245
+ checked: formData.active,
1246
+ onCheckedChange: (checked) => setFormData({ ...formData, active: checked })
776
1247
  }
777
1248
  ),
778
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-sm text-ui-fg-subtle", children: "Active" })
1249
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "active", children: "เปิดใช้งาน" })
779
1250
  ] }),
780
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-3", children: [
781
- /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { onClick: handleSubmit, disabled: saving, children: formState.id ? "Update" : "Create" }),
782
- formState.id && /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", onClick: resetForm, children: "Cancel" })
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 ? "กำลังบันทึก..." : "บันทึก" })
783
1263
  ] })
784
- ] }),
785
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "overflow-hidden rounded-md border", children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Table, { children: [
786
- /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Header, { children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
787
- /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Name" }),
788
- /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Dimensions (cm)" }),
789
- /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Max Weight (kg)" }),
790
- /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Price" }),
791
- /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Status" }),
792
- /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { className: "text-right", children: "Actions" })
793
- ] }) }),
794
- /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Body, { children: tableRows })
795
1264
  ] }) })
796
- ] });
1265
+ ] }) });
797
1266
  };
798
- const RatesSection = () => {
799
- const [rates, setRates] = react.useState([]);
800
- const [loading, setLoading] = react.useState(true);
801
- const [formState, setFormState] = react.useState({
802
- id: null,
803
- carrier: "COMPANY_TRUCK",
804
- min_weight_kg: "0",
805
- max_weight_kg: "",
806
- price_thb: "",
807
- currency: "THB",
808
- active: true
809
- });
810
- const [currencyOptions, setCurrencyOptions] = react.useState([]);
811
- const [defaultCurrency, setDefaultCurrency] = react.useState("THB");
812
- const [currenciesLoading, setCurrenciesLoading] = react.useState(true);
813
- const [saving, setSaving] = react.useState(false);
814
- const [deletingId, setDeletingId] = react.useState(null);
815
- const refresh = react.useCallback(async () => {
816
- setLoading(true);
817
- try {
818
- const data = await requestJson(
819
- "/admin/shipping-config/rates"
820
- );
821
- setRates(
822
- (data.rates ?? []).map((rate) => ({
823
- ...rate,
824
- currency: (rate.currency ?? "").toUpperCase()
825
- }))
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())
826
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 || []);
827
1309
  } catch (error) {
828
- ui.toast.error(error.message ?? "Failed to load rates", {
829
- dismissable: true
1310
+ ui.toast.error("ข้อผิดพลาด", {
1311
+ description: "ไม่สามารถโหลดข้อมูลต้นทุนวัสดุได้"
830
1312
  });
831
1313
  } finally {
832
- setLoading(false);
1314
+ setIsLoading(false);
833
1315
  }
834
- }, []);
835
- const loadCurrencies = react.useCallback(async () => {
836
- var _a, _b, _c;
837
- setCurrenciesLoading(true);
1316
+ };
1317
+ const handleToggleActive = async (materialCost) => {
838
1318
  try {
839
- const data = await requestJson("/admin/currencies");
840
- const supported = ((_b = (_a = data.store) == null ? void 0 : _a.supported_currencies) == null ? void 0 : _b.map(
841
- (item) => item.code.toUpperCase()
842
- )) ?? [];
843
- const defaultCode = (((_c = data.store) == null ? void 0 : _c.default_currency_code) ?? supported[0] ?? "THB").toUpperCase();
844
- const options = supported.length > 0 ? supported : [defaultCode];
845
- setCurrencyOptions(options);
846
- setDefaultCurrency(defaultCode);
847
- setFormState((prev) => ({
848
- ...prev,
849
- currency: prev.currency && options.includes(prev.currency.toUpperCase()) ? prev.currency.toUpperCase() : defaultCode
850
- }));
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
+ }
851
1333
  } catch (error) {
852
- ui.toast.error(
853
- error.message ?? "Failed to load store currencies",
854
- { dismissable: true }
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
+ }
855
1348
  );
856
- setCurrencyOptions((prev) => prev.length > 0 ? prev : ["THB"]);
857
- setDefaultCurrency((prev) => prev || "THB");
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
+ });
858
1361
  } finally {
859
- setCurrenciesLoading(false);
1362
+ setDeleteMaterialCostId(null);
1363
+ setDeleteMaterialCostName("");
860
1364
  }
861
- }, []);
862
- react.useEffect(() => {
863
- refresh();
864
- }, [refresh]);
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: [
1392
+ /* @__PURE__ */ jsxRuntime.jsx(
1393
+ ui.Input,
1394
+ {
1395
+ type: "search",
1396
+ placeholder: "ค้นหาชื่อวัสดุ...",
1397
+ value: searchTerm,
1398
+ onChange: (e) => setSearchTerm(e.target.value),
1399
+ className: "max-w-md"
1400
+ }
1401
+ ),
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
+ ] })
1406
+ ] }),
1407
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Button, { onClick: handleCreateNew, children: [
1408
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, {}),
1409
+ "สร้างรายการใหม่"
1410
+ ] })
1411
+ ] }),
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)) })
1462
+ ] })
1463
+ ] }),
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
+ )
1496
+ ] });
1497
+ };
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",
1523
+ max_weight_kg: "",
1524
+ price: "",
1525
+ currency: "THB",
1526
+ priority: "0",
1527
+ eta_hours_min: "",
1528
+ eta_hours_max: "",
1529
+ eta_days_min: "",
1530
+ eta_days_max: "",
1531
+ active: true
1532
+ });
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;
865
1536
  react.useEffect(() => {
866
- loadCurrencies();
867
- }, [loadCurrencies]);
868
- const getInitialCurrency = react.useCallback(() => {
869
- if (currencyOptions.length === 0) {
870
- return defaultCurrency || "THB";
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
1551
+ });
871
1552
  }
872
- return currencyOptions.includes(defaultCurrency) ? defaultCurrency : currencyOptions[0];
873
- }, [currencyOptions, defaultCurrency]);
874
- const resetForm = react.useCallback(() => {
875
- setFormState({
876
- id: null,
877
- carrier: "COMPANY_TRUCK",
878
- min_weight_kg: "0",
879
- max_weight_kg: "",
880
- price_thb: "",
881
- currency: getInitialCurrency(),
882
- active: true
883
- });
884
- }, [getInitialCurrency]);
885
- react.useEffect(() => {
886
- if (!formState.id && !currencyOptions.includes(formState.currency)) {
887
- setFormState((prev) => ({
888
- ...prev,
889
- currency: getInitialCurrency()
890
- }));
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");
891
1562
  }
892
- }, [currencyOptions, formState.currency, formState.id, getInitialCurrency]);
893
- const handleSubmit = async () => {
894
- const min = toNumber(formState.min_weight_kg);
895
- const max = toNumber(formState.max_weight_kg);
896
- const price = toNumber(formState.price_thb);
897
- if (max === null || price === null || max <= 0) {
898
- ui.toast.error("Provide valid max weight and price", { dismissable: true });
899
- return;
1563
+ if (isNaN(price) || price < 0) {
1564
+ errors.push("ราคาต้องเป็นตัวเลขไม่ติดลบ");
1565
+ }
1566
+ if (isNaN(priority)) {
1567
+ errors.push("ลำดับความสำคัญต้องเป็นตัวเลข");
1568
+ }
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
+ }
900
1589
  }
901
- if (min !== null && max <= min) {
902
- ui.toast.error("Max weight must be greater than min weight", {
903
- dismissable: true
1590
+ if (errors.length > 0) {
1591
+ ui.toast.error("ข้อมูลไม่ถูกต้อง", {
1592
+ description: errors.join(", ")
904
1593
  });
905
1594
  return;
906
1595
  }
907
- const payload = {
908
- carrier: formState.carrier,
909
- min_weight_kg: min ?? 0,
910
- max_weight_kg: max,
911
- price_thb: price,
912
- currency: (formState.currency || getInitialCurrency()).trim().toUpperCase(),
913
- active: formState.active
914
- };
915
- setSaving(true);
1596
+ setIsSaving(true);
916
1597
  try {
917
- if (formState.id) {
918
- await requestJson(
919
- `/admin/shipping-config/rates/${formState.id}`,
920
- "PUT",
921
- payload
922
- );
923
- 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;
924
1610
  } else {
925
- await requestJson("/admin/shipping-config/rates", "POST", payload);
926
- ui.toast.success("Rate created", { dismissable: true });
1611
+ payload.eta_days_min = etaDaysMin;
1612
+ payload.eta_days_max = etaDaysMax;
927
1613
  }
928
- await refresh();
929
- resetForm();
930
- } catch (error) {
931
- ui.toast.error(error.message ?? "Failed to save rate", {
932
- dismissable: true
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)
933
1621
  });
934
- } finally {
935
- setSaving(false);
936
- }
937
- };
938
- const handleDelete = async (id) => {
939
- setDeletingId(id);
940
- try {
941
- await requestJson(`/admin/shipping-config/rates/${id}`, "DELETE");
942
- ui.toast.success("Rate removed", { dismissable: true });
943
- 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
+ }
944
1631
  } catch (error) {
945
- ui.toast.error(error.message ?? "Failed to delete rate", {
946
- dismissable: true
1632
+ ui.toast.error("ข้อผิดพลาด", {
1633
+ description: error instanceof Error ? error.message : "ไม่สามารถบันทึกข้อมูลได้"
947
1634
  });
948
1635
  } finally {
949
- setDeletingId(null);
950
- }
951
- };
952
- const handleToggle = async (rate) => {
953
- try {
954
- await requestJson(`/admin/shipping-config/rates/${rate.id}`, "PUT", {
955
- active: !rate.active
956
- });
957
- await refresh();
958
- } catch (error) {
959
- ui.toast.error(error.message ?? "Failed to update rate status", {
960
- dismissable: true
961
- });
1636
+ setIsSaving(false);
962
1637
  }
963
1638
  };
964
- const tableRows = react.useMemo(() => {
965
- if (loading) {
966
- 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..." }) }) });
967
- }
968
- if (rates.length === 0) {
969
- 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." }) }) });
970
- }
971
- return rates.map((rate) => /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
972
- /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "font-medium", children: rate.carrier === "COMPANY_TRUCK" ? "Company Truck" : "Private Carrier" }) }),
973
- /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "text-sm text-ui-fg-subtle", children: [
974
- rate.min_weight_kg,
975
- " - ",
976
- rate.max_weight_kg
977
- ] }) }),
978
- /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Cell, { children: [
979
- rate.price_thb,
980
- " ",
981
- rate.currency
982
- ] }),
983
- /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { color: rate.active ? "green" : "grey", children: rate.active ? "Active" : "Inactive" }) }),
984
- /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-end gap-2", children: [
985
- /* @__PURE__ */ jsxRuntime.jsx(
986
- 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,
987
1646
  {
988
- checked: rate.active,
989
- 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
+ ]
990
1653
  }
991
- ),
992
- /* @__PURE__ */ jsxRuntime.jsx(
993
- 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,
994
1660
  {
995
- variant: "secondary",
996
- size: "small",
997
- onClick: () => setFormState({
998
- id: rate.id,
999
- carrier: rate.carrier,
1000
- min_weight_kg: String(rate.min_weight_kg ?? 0),
1001
- max_weight_kg: String(rate.max_weight_kg),
1002
- price_thb: String(rate.price_thb),
1003
- currency: (rate.currency ?? "").toUpperCase(),
1004
- 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: ""
1005
1669
  }),
1006
- children: "Edit"
1007
- }
1008
- ),
1009
- /* @__PURE__ */ jsxRuntime.jsx(
1010
- ui.Button,
1011
- {
1012
- variant: "danger",
1013
- size: "small",
1014
- onClick: () => handleDelete(rate.id),
1015
- disabled: deletingId === rate.id,
1016
- 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
+ ]
1017
1674
  }
1018
1675
  )
1019
- ] }) })
1020
- ] }, rate.id));
1021
- }, [deletingId, loading, rates]);
1022
- return /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "space-y-6", children: [
1023
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-4 rounded-md border p-4", children: [
1024
- /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: formState.id ? "Edit Shipping Rate" : "Create Shipping Rate" }),
1025
- /* @__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: [
1026
1678
  /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1679
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "maxWeight", children: "น้ำหนักสูงสุด (kg) *" }),
1027
1680
  /* @__PURE__ */ jsxRuntime.jsx(
1028
- ui.Label,
1029
- {
1030
- className: "text-sm font-medium text-ui-fg-base",
1031
- htmlFor: "parcel-rate-carrier",
1032
- children: "Carrier"
1033
- }
1034
- ),
1035
- /* @__PURE__ */ jsxRuntime.jsxs(
1036
- "select",
1681
+ ui.Input,
1037
1682
  {
1038
- id: "parcel-rate-carrier",
1039
- className: "mt-1 w-full rounded-md border border-ui-border-base bg-ui-bg-field px-3 py-2 text-sm",
1040
- value: formState.carrier,
1041
- onChange: (event) => setFormState((prev) => ({
1042
- ...prev,
1043
- carrier: event.target.value
1044
- })),
1045
- children: [
1046
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "COMPANY_TRUCK", children: "Company Truck" }),
1047
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "PRIVATE_CARRIER", children: "Private Carrier" })
1048
- ]
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
1049
1691
  }
1050
1692
  )
1051
1693
  ] }),
1052
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
1053
- /* @__PURE__ */ jsxRuntime.jsx(
1054
- ui.Label,
1055
- {
1056
- className: "text-sm font-medium text-ui-fg-base",
1057
- htmlFor: "parcel-rate-min-weight",
1058
- children: "Min Weight (kg)"
1059
- }
1060
- ),
1694
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1695
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "price", children: "ราคา (THB) *" }),
1061
1696
  /* @__PURE__ */ jsxRuntime.jsx(
1062
1697
  ui.Input,
1063
1698
  {
1064
- id: "parcel-rate-min-weight",
1065
- placeholder: "e.g. 0",
1699
+ id: "price",
1066
1700
  type: "number",
1067
- value: formState.min_weight_kg,
1068
- onChange: (event) => setFormState((prev) => ({
1069
- ...prev,
1070
- min_weight_kg: event.target.value
1071
- }))
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
1072
1707
  }
1073
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
+ " *"
1074
1729
  ] }),
1075
- /* @__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: [
1076
1731
  /* @__PURE__ */ jsxRuntime.jsx(
1077
- ui.Label,
1732
+ ui.Input,
1078
1733
  {
1079
- className: "text-sm font-medium text-ui-fg-base",
1080
- htmlFor: "parcel-rate-max-weight",
1081
- 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
1082
1739
  }
1083
1740
  ),
1084
1741
  /* @__PURE__ */ jsxRuntime.jsx(
1085
1742
  ui.Input,
1086
1743
  {
1087
- id: "parcel-rate-max-weight",
1088
- placeholder: "e.g. 5",
1089
1744
  type: "number",
1090
- value: formState.max_weight_kg,
1091
- onChange: (event) => setFormState((prev) => ({
1092
- ...prev,
1093
- max_weight_kg: event.target.value
1094
- }))
1745
+ placeholder: "สูงสุด",
1746
+ value: formData.eta_hours_max,
1747
+ onChange: (e) => setFormData({ ...formData, eta_hours_max: e.target.value }),
1748
+ required: true
1095
1749
  }
1096
1750
  )
1097
- ] }),
1098
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
1751
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1099
1752
  /* @__PURE__ */ jsxRuntime.jsx(
1100
- ui.Label,
1753
+ ui.Input,
1101
1754
  {
1102
- className: "text-sm font-medium text-ui-fg-base",
1103
- htmlFor: "parcel-rate-price",
1104
- 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
1105
1760
  }
1106
1761
  ),
1107
1762
  /* @__PURE__ */ jsxRuntime.jsx(
1108
1763
  ui.Input,
1109
1764
  {
1110
- id: "parcel-rate-price",
1111
- placeholder: "e.g. 45",
1112
1765
  type: "number",
1113
- value: formState.price_thb,
1114
- onChange: (event) => setFormState((prev) => ({
1115
- ...prev,
1116
- price_thb: event.target.value
1117
- }))
1766
+ placeholder: "สูงสุด",
1767
+ value: formData.eta_days_max,
1768
+ onChange: (e) => setFormData({ ...formData, eta_days_max: e.target.value }),
1769
+ required: true
1118
1770
  }
1119
1771
  )
1120
- ] }),
1121
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
1122
- /* @__PURE__ */ jsxRuntime.jsx(
1123
- ui.Label,
1124
- {
1125
- className: "text-sm font-medium text-ui-fg-base",
1126
- htmlFor: "parcel-rate-currency",
1127
- children: "Currency"
1128
- }
1129
- ),
1130
- /* @__PURE__ */ jsxRuntime.jsx(
1131
- "select",
1132
- {
1133
- id: "parcel-rate-currency",
1134
- className: "w-full rounded-md border border-ui-border-base bg-ui-bg-field px-3 py-2 text-sm",
1135
- value: formState.currency,
1136
- onChange: (event) => setFormState((prev) => ({
1137
- ...prev,
1138
- currency: event.target.value
1139
- })),
1140
- disabled: currencyOptions.length === 0,
1141
- children: currencyOptions.map((code) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: code, children: code }, code))
1142
- }
1143
- ),
1144
- currenciesLoading && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-xs text-ui-fg-subtle", children: "Loading currencies..." }),
1145
- !currenciesLoading && currencyOptions.length === 0 && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-xs text-ui-fg-subtle", children: "Configure store currencies to enable selection." })
1146
- ] })
1772
+ ] }) })
1147
1773
  ] }),
1148
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
1774
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-x-2", children: [
1149
1775
  /* @__PURE__ */ jsxRuntime.jsx(
1150
1776
  ui.Switch,
1151
1777
  {
1152
- checked: formState.active,
1153
- onCheckedChange: (active) => setFormState((prev) => ({ ...prev, active }))
1778
+ id: "active",
1779
+ checked: formData.active,
1780
+ onCheckedChange: (checked) => setFormData({ ...formData, active: checked })
1154
1781
  }
1155
1782
  ),
1156
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-sm text-ui-fg-subtle", children: "Active" })
1783
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "active", children: "เปิดใช้งาน" })
1157
1784
  ] }),
1158
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-3", children: [
1159
- /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { onClick: handleSubmit, disabled: saving, children: formState.id ? "Update" : "Create" }),
1160
- 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 ? "กำลังบันทึก..." : "บันทึก" })
1161
1788
  ] })
1162
- ] }),
1163
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "overflow-hidden rounded-md border", children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Table, { children: [
1164
- /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Header, { children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
1165
- /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Carrier" }),
1166
- /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Weight Range (kg)" }),
1167
- /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Price" }),
1168
- /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Status" }),
1169
- /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { className: "text-right", children: "Actions" })
1170
- ] }) }),
1171
- /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Body, { children: tableRows })
1172
1789
  ] }) })
1173
- ] });
1790
+ ] }) });
1174
1791
  };
1175
- const AreasSection = () => {
1176
- const [areas, setAreas] = react.useState([]);
1177
- const [loading, setLoading] = react.useState(true);
1178
- const [formState, setFormState] = react.useState({
1179
- id: null,
1180
- kind: "PROVINCE",
1181
- value: "",
1182
- active: true
1183
- });
1184
- const thaiAddressInputs = react.useMemo(() => getThaiAddressComponents(), []);
1185
- const ThaiProvinceInput = thaiAddressInputs == null ? void 0 : thaiAddressInputs.Province;
1186
- const ThaiZipcodeInput = thaiAddressInputs == null ? void 0 : thaiAddressInputs.Zipcode;
1187
- const [thaiAddress, setThaiAddress] = react.useState({
1188
- district: "",
1189
- amphoe: "",
1190
- province: "",
1191
- zipcode: ""
1192
- });
1193
- const [saving, setSaving] = react.useState(false);
1194
- const [deletingId, setDeletingId] = react.useState(null);
1195
- const refresh = react.useCallback(async () => {
1196
- setLoading(true);
1197
- try {
1198
- const data = await requestJson(
1199
- "/admin/shipping-config/areas"
1200
- );
1201
- setAreas(data.areas ?? []);
1202
- } catch (error) {
1203
- ui.toast.error(error.message ?? "Failed to load service areas", {
1204
- dismissable: true
1205
- });
1206
- } finally {
1207
- setLoading(false);
1208
- }
1209
- }, []);
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);
1210
1814
  react.useEffect(() => {
1211
- refresh();
1212
- }, [refresh]);
1213
- const resetForm = react.useCallback(() => {
1214
- setFormState({
1215
- id: null,
1216
- kind: "PROVINCE",
1217
- value: "",
1218
- active: true
1219
- });
1220
- setThaiAddress({
1221
- district: "",
1222
- amphoe: "",
1223
- province: "",
1224
- zipcode: ""
1225
- });
1815
+ fetchRates();
1226
1816
  }, []);
1227
- const handleSubmit = async () => {
1228
- if (!formState.value.trim()) {
1229
- ui.toast.error("Provide a value", { dismissable: true });
1230
- return;
1817
+ react.useEffect(() => {
1818
+ let filtered = rates;
1819
+ if (carrierFilter !== "ALL") {
1820
+ filtered = filtered.filter((rate) => rate.carrier_type === carrierFilter);
1231
1821
  }
1232
- const payload = {
1233
- kind: formState.kind,
1234
- value: formState.value.trim(),
1235
- active: formState.active
1236
- };
1237
- setSaving(true);
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);
1238
1829
  try {
1239
- if (formState.id) {
1240
- await requestJson(
1241
- `/admin/shipping-config/areas/${formState.id}`,
1242
- "PUT",
1243
- payload
1244
- );
1245
- ui.toast.success("Service area updated", { dismissable: true });
1246
- } else {
1247
- await requestJson("/admin/shipping-config/areas", "POST", payload);
1248
- ui.toast.success("Service area created", { dismissable: true });
1249
- }
1250
- await refresh();
1251
- resetForm();
1830
+ const response = await fetch("/admin/shipping-rates", {
1831
+ credentials: "include"
1832
+ });
1833
+ const data = await response.json();
1834
+ setRates(data.rates || []);
1252
1835
  } catch (error) {
1253
- ui.toast.error(error.message ?? "Failed to save service area", {
1254
- dismissable: true
1836
+ ui.toast.error("ข้อผิดพลาด", {
1837
+ description: "ไม่สามารถโหลดข้อมูลอัตราค่าขนส่งได้"
1255
1838
  });
1256
1839
  } finally {
1257
- setSaving(false);
1840
+ setIsLoading(false);
1258
1841
  }
1259
1842
  };
1260
- const handleDelete = async (id) => {
1261
- setDeletingId(id);
1843
+ const handleDelete = async () => {
1844
+ if (!deleteRateId) return;
1262
1845
  try {
1263
- await requestJson(`/admin/shipping-config/areas/${id}`, "DELETE");
1264
- ui.toast.success("Service area removed", { dismissable: true });
1265
- await refresh();
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: "ลบอัตราค่าขนส่งแล้ว"
1853
+ });
1854
+ fetchRates();
1855
+ } else {
1856
+ throw new Error("Failed to delete");
1857
+ }
1266
1858
  } catch (error) {
1267
- ui.toast.error(error.message ?? "Failed to delete service area", {
1268
- dismissable: true
1859
+ ui.toast.error("ข้อผิดพลาด", {
1860
+ description: "ไม่สามารถลบอัตราค่าขนส่งได้"
1269
1861
  });
1270
1862
  } finally {
1271
- setDeletingId(null);
1863
+ setDeleteRateId(null);
1272
1864
  }
1273
1865
  };
1274
- const handleToggle = async (area) => {
1275
- try {
1276
- await requestJson(`/admin/shipping-config/areas/${area.id}`, "PUT", {
1277
- active: !area.active
1278
- });
1279
- await refresh();
1280
- } catch (error) {
1281
- ui.toast.error(
1282
- error.message ?? "Failed to update service area status",
1283
- { dismissable: true }
1284
- );
1285
- }
1866
+ const handleEdit = (rate) => {
1867
+ setEditingRate(rate);
1868
+ setIsModalOpen(true);
1286
1869
  };
1287
- const beginEdit = react.useCallback((area) => {
1288
- setFormState({
1289
- id: area.id,
1290
- kind: area.kind,
1291
- value: area.value,
1292
- active: area.active
1293
- });
1294
- setThaiAddress((prev) => ({
1295
- ...prev,
1296
- province: area.kind === "PROVINCE" ? area.value : prev.province,
1297
- zipcode: area.kind === "POSTCODE_PREFIX" ? area.value : prev.zipcode
1298
- }));
1299
- }, []);
1300
- const handleThaiAddressSelect = react.useCallback(
1301
- (addressRecord) => {
1302
- const nextAddress = {
1303
- district: addressRecord.district ?? "",
1304
- amphoe: addressRecord.amphoe ?? "",
1305
- province: addressRecord.province ?? "",
1306
- zipcode: addressRecord.zipcode ? String(addressRecord.zipcode) : ""
1307
- };
1308
- setThaiAddress(nextAddress);
1309
- if (formState.kind === "PROVINCE") {
1310
- setFormState((prev) => ({
1311
- ...prev,
1312
- value: nextAddress.province
1313
- }));
1314
- } else {
1315
- setFormState((prev) => ({
1316
- ...prev,
1317
- value: nextAddress.zipcode.slice(0, 3)
1318
- }));
1319
- }
1320
- },
1321
- [formState.kind]
1322
- );
1323
- const handleThaiAddressChange = react.useCallback(
1324
- (scope) => (rawValue) => {
1325
- const value = typeof rawValue === "number" ? String(rawValue) : rawValue;
1326
- setThaiAddress((prev) => ({
1327
- ...prev,
1328
- [scope]: value
1329
- }));
1330
- if (scope === "province" && formState.kind === "PROVINCE") {
1331
- setFormState((prev) => ({
1332
- ...prev,
1333
- value
1334
- }));
1335
- }
1336
- if (scope === "zipcode" && formState.kind === "POSTCODE_PREFIX") {
1337
- setFormState((prev) => ({
1338
- ...prev,
1339
- value: value.slice(0, 3)
1340
- }));
1341
- }
1342
- },
1343
- [formState.kind]
1344
- );
1345
- const tableRows = react.useMemo(() => {
1346
- if (loading) {
1347
- 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..." }) }) });
1348
- }
1349
- if (areas.length === 0) {
1350
- 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." }) }) });
1870
+ const handleCreateNew = () => {
1871
+ setEditingRate(null);
1872
+ setIsModalOpen(true);
1873
+ };
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} ชม.`;
1351
1890
  }
1352
- return areas.map((area) => /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
1353
- /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "font-medium", children: area.kind === "PROVINCE" ? "Province" : "Postcode Prefix" }) }),
1354
- /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: area.value }),
1355
- /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { color: area.active ? "green" : "grey", children: area.active ? "Active" : "Inactive" }) }),
1356
- /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-end gap-2", children: [
1357
- /* @__PURE__ */ jsxRuntime.jsx(
1358
- ui.Switch,
1359
- {
1360
- checked: area.active,
1361
- onCheckedChange: () => handleToggle(area)
1362
- }
1363
- ),
1364
- /* @__PURE__ */ jsxRuntime.jsx(
1365
- ui.Button,
1366
- {
1367
- variant: "secondary",
1368
- size: "small",
1369
- onClick: () => beginEdit(area),
1370
- children: "Edit"
1371
- }
1372
- ),
1373
- /* @__PURE__ */ jsxRuntime.jsx(
1374
- ui.Button,
1375
- {
1376
- variant: "danger",
1377
- size: "small",
1378
- onClick: () => handleDelete(area.id),
1379
- disabled: deletingId === area.id,
1380
- children: "Delete"
1381
- }
1382
- )
1383
- ] }) })
1384
- ] }, area.id));
1385
- }, [areas, beginEdit, deletingId, handleDelete, handleToggle, loading]);
1386
- const valueLabel = formState.kind === "PROVINCE" ? "Province" : "Postcode Prefix";
1387
- react.useEffect(() => {
1388
- if (formState.kind === "PROVINCE") {
1389
- setThaiAddress(
1390
- (prev) => prev.province === formState.value ? prev : { ...prev, province: formState.value }
1391
- );
1392
- } else if (formState.kind === "POSTCODE_PREFIX") {
1393
- setThaiAddress(
1394
- (prev) => prev.zipcode === formState.value ? prev : { ...prev, zipcode: formState.value }
1395
- );
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} วัน`;
1396
1893
  }
1397
- }, [formState.kind, formState.value]);
1398
- return /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "space-y-6", children: [
1399
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-4 rounded-md border p-4", children: [
1400
- /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: formState.id ? "Edit Service Area" : "Create Service Area" }),
1401
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-4 md:grid-cols-3", children: [
1402
- /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1403
- /* @__PURE__ */ jsxRuntime.jsx(
1404
- ui.Label,
1405
- {
1406
- className: "text-sm font-medium text-ui-fg-base",
1407
- htmlFor: "parcel-area-kind",
1408
- children: "Kind"
1409
- }
1410
- ),
1411
- /* @__PURE__ */ jsxRuntime.jsxs(
1412
- "select",
1413
- {
1414
- id: "parcel-area-kind",
1415
- className: "mt-1 w-full rounded-md border border-ui-border-base bg-ui-bg-field px-3 py-2 text-sm",
1416
- value: formState.kind,
1417
- onChange: (event) => {
1418
- const nextKind = event.target.value;
1419
- setFormState((prev) => ({
1420
- ...prev,
1421
- kind: nextKind,
1422
- value: nextKind === "PROVINCE" ? thaiAddress.province : thaiAddress.zipcode.slice(0, 3)
1423
- }));
1424
- },
1425
- children: [
1426
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "PROVINCE", children: "Province" }),
1427
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "POSTCODE_PREFIX", children: "Postcode Prefix" })
1428
- ]
1429
- }
1430
- )
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
+ ] })
1431
1914
  ] }),
1432
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
1433
- /* @__PURE__ */ jsxRuntime.jsx(
1434
- ui.Label,
1435
- {
1436
- className: "text-sm font-medium text-ui-fg-base",
1437
- htmlFor: "parcel-area-value",
1438
- children: valueLabel
1439
- }
1440
- ),
1441
- ThaiProvinceInput && ThaiZipcodeInput ? formState.kind === "PROVINCE" ? /* @__PURE__ */ jsxRuntime.jsx(
1442
- ThaiProvinceInput,
1443
- {
1444
- className: "w-full",
1445
- value: thaiAddress.province,
1446
- onChange: handleThaiAddressChange("province"),
1447
- onSelect: handleThaiAddressSelect,
1448
- autoCompleteProps: {
1449
- style: { width: "100%" },
1450
- dropdownMatchSelectWidth: false,
1451
- placement: "bottomLeft"
1452
- }
1453
- }
1454
- ) : /* @__PURE__ */ jsxRuntime.jsx(
1455
- ThaiZipcodeInput,
1456
- {
1457
- className: "w-full",
1458
- value: thaiAddress.zipcode,
1459
- onChange: handleThaiAddressChange("zipcode"),
1460
- onSelect: handleThaiAddressSelect,
1461
- autoCompleteProps: {
1462
- style: { width: "100%" },
1463
- dropdownMatchSelectWidth: false,
1464
- placement: "bottomLeft"
1465
- }
1466
- }
1467
- ) : /* @__PURE__ */ jsxRuntime.jsx(
1468
- ui.Input,
1469
- {
1470
- id: "parcel-area-value",
1471
- placeholder: `Enter ${valueLabel.toLowerCase()}`,
1472
- value: formState.value,
1473
- onChange: (event) => setFormState((prev) => ({
1474
- ...prev,
1475
- value: event.target.value
1476
- }))
1477
- }
1478
- ),
1479
- 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
+ "สร้างอัตราใหม่"
1480
1918
  ] })
1481
1919
  ] }),
1482
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
1483
- /* @__PURE__ */ jsxRuntime.jsx(
1484
- ui.Switch,
1485
- {
1486
- checked: formState.active,
1487
- onCheckedChange: (active) => setFormState((prev) => ({ ...prev, active }))
1488
- }
1489
- ),
1490
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-sm text-ui-fg-subtle", children: "Active" })
1491
- ] }),
1492
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-3", children: [
1493
- /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { onClick: handleSubmit, disabled: saving, children: formState.id ? "Update" : "Create" }),
1494
- 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)) })
1495
1962
  ] })
1496
1963
  ] }),
1497
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "overflow-hidden rounded-md border", children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Table, { children: [
1498
- /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Header, { children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
1499
- /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Kind" }),
1500
- /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Value" }),
1501
- /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Status" }),
1502
- /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { className: "text-right", children: "Actions" })
1503
- ] }) }),
1504
- /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Body, { children: tableRows })
1505
- ] }) })
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
+ )
1506
1983
  ] });
1507
1984
  };
1508
- const ParcelShippingRoute = () => {
1509
- 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
+ ] }) });
1510
1990
  };
1511
1991
  const config = adminSdk.defineRouteConfig({
1512
- label: "Parcel Shipping",
1513
- icon: icons.Directions
1992
+ icon: icons.HandTruck,
1993
+ label: "อัตราค่าขนส่ง"
1514
1994
  });
1515
- const widgetModule = { widgets: [] };
1995
+ const widgetModule = { widgets: [
1996
+ {
1997
+ Component: OrderShippingQuoteWidget,
1998
+ zone: ["order.details.after"]
1999
+ }
2000
+ ] };
1516
2001
  const routeModule = {
1517
2002
  routes: [
1518
2003
  {
1519
- Component: ParcelBoxesRoute,
1520
- path: "/parcel-boxes"
2004
+ Component: AreasPage,
2005
+ path: "/areas"
2006
+ },
2007
+ {
2008
+ Component: BoxesPage,
2009
+ path: "/boxes"
1521
2010
  },
1522
2011
  {
1523
- Component: ParcelShippingRoute,
1524
- path: "/parcel-shipping"
2012
+ Component: MaterialCostsPage,
2013
+ path: "/material-costs"
2014
+ },
2015
+ {
2016
+ Component: RatesPage,
2017
+ path: "/rates"
1525
2018
  }
1526
2019
  ]
1527
2020
  };
1528
2021
  const menuItemModule = {
1529
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
+ },
1530
2035
  {
1531
2036
  label: config$1.label,
1532
2037
  icon: config$1.icon,
1533
- path: "/parcel-boxes",
2038
+ path: "/material-costs",
1534
2039
  nested: void 0
1535
2040
  },
1536
2041
  {
1537
2042
  label: config.label,
1538
2043
  icon: config.icon,
1539
- path: "/parcel-shipping",
2044
+ path: "/rates",
1540
2045
  nested: void 0
1541
2046
  }
1542
2047
  ]