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