@lodashventure/medusa-parcel-shipping 0.4.31 → 0.4.34

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.
@@ -3,7 +3,7 @@ import { defineWidgetConfig, defineRouteConfig } from "@medusajs/admin-sdk";
3
3
  import { Container, Heading, Button, Text, Badge, Drawer, Label, Select, Input, Switch, toast, Table, Prompt, Textarea } from "@medusajs/ui";
4
4
  import { useState, useEffect, useMemo } from "react";
5
5
  import { Package, AlertCircle, Truck, Clock, Plus, Edit, Trash, ExternalLink } from "lucide-react";
6
- import { MapPin, Directions, ArchiveBox, HandTruck, CurrencyDollarSolid } from "@medusajs/icons";
6
+ import { MapPin, Directions, ArchiveBox, CurrencyDollarSolid, HandTruck } from "@medusajs/icons";
7
7
  import "@medusajs/admin-shared";
8
8
  const OrderShippingQuoteWidget = ({ data }) => {
9
9
  const [quote, setQuote] = useState(null);
@@ -1426,88 +1426,53 @@ const config$2 = defineRouteConfig({
1426
1426
  icon: ArchiveBox,
1427
1427
  label: "กล่องพัสดุ"
1428
1428
  });
1429
- const CARRIER_TYPES$1 = [
1430
- { value: "COMPANY_FLEET", label: "บริษัทรถส่งของ (Messenger)" },
1431
- { value: "COMPANY_TRUCK", label: "รถบริษัท (Same Day)" },
1432
- { value: "PRIVATE_CARRIER", label: "ขนส่งเอกชน (EMS/Express)" }
1433
- ];
1434
- const SERVICE_CODES$1 = [
1435
- { value: "MESSENGER_3H", label: "ส่งด่วน 3 ชม.", usesHours: true },
1436
- { value: "SAME_DAY", label: "ส่งด่วนภายในวัน", usesHours: true },
1437
- { value: "STANDARD_3_5D", label: "EMS 3-5 วัน", usesHours: false },
1438
- { value: "EXPRESS_1_2D", label: "Express 1-2 วัน", usesHours: false }
1429
+ const CATEGORIES$1 = [
1430
+ { value: "PACKAGING", label: "วัสดุหีบห่อ" },
1431
+ { value: "PROTECTION", label: "วัสดุป้องกัน" },
1432
+ { value: "FILLING", label: "วัสดุเติมช่องว่าง" },
1433
+ { value: "TAPE", label: "เทป/กาว" },
1434
+ { value: "LABEL", label: "ฉลาก/สติกเกอร์" },
1435
+ { value: "OTHER", label: "อื่นๆ" }
1439
1436
  ];
1440
- const ShippingRateModal = ({ rate, onClose }) => {
1437
+ const MaterialCostModal = ({
1438
+ materialCost,
1439
+ onClose
1440
+ }) => {
1441
1441
  const [formData, setFormData] = useState({
1442
- carrier_type: "COMPANY_FLEET",
1443
- service_code: "MESSENGER_3H",
1444
- max_weight_kg: "",
1445
- price: "",
1442
+ name: "",
1443
+ unit: "",
1444
+ cost_per_unit: "",
1446
1445
  currency: "THB",
1447
- priority: "0",
1448
- eta_hours_min: "",
1449
- eta_hours_max: "",
1450
- eta_days_min: "",
1451
- eta_days_max: "",
1446
+ category: "",
1447
+ description: "",
1452
1448
  active: true
1453
1449
  });
1454
1450
  const [isSaving, setIsSaving] = useState(false);
1455
- const selectedService = SERVICE_CODES$1.find((s) => s.value === formData.service_code);
1456
- const usesHours = (selectedService == null ? void 0 : selectedService.usesHours) ?? false;
1457
- const isMessenger = formData.carrier_type === "COMPANY_FLEET" && formData.service_code === "MESSENGER_3H";
1458
1451
  useEffect(() => {
1459
- var _a, _b, _c, _d;
1460
- if (rate) {
1452
+ if (materialCost) {
1461
1453
  setFormData({
1462
- carrier_type: rate.carrier_type,
1463
- service_code: rate.service_code,
1464
- max_weight_kg: rate.max_weight_kg.toString(),
1465
- price: rate.price.toString(),
1466
- currency: rate.currency,
1467
- priority: (rate.priority ?? 0).toString(),
1468
- eta_hours_min: ((_a = rate.eta_hours_min) == null ? void 0 : _a.toString()) || "",
1469
- eta_hours_max: ((_b = rate.eta_hours_max) == null ? void 0 : _b.toString()) || "",
1470
- eta_days_min: ((_c = rate.eta_days_min) == null ? void 0 : _c.toString()) || "",
1471
- eta_days_max: ((_d = rate.eta_days_max) == null ? void 0 : _d.toString()) || "",
1472
- active: rate.active
1454
+ name: materialCost.name,
1455
+ unit: materialCost.unit,
1456
+ cost_per_unit: materialCost.cost_per_unit.toString(),
1457
+ currency: materialCost.currency,
1458
+ category: materialCost.category || "",
1459
+ description: materialCost.description || "",
1460
+ active: materialCost.active
1473
1461
  });
1474
1462
  }
1475
- }, [rate]);
1463
+ }, [materialCost]);
1476
1464
  const handleSubmit = async (e) => {
1477
1465
  e.preventDefault();
1478
1466
  const errors = [];
1479
- const maxWeight = parseFloat(formData.max_weight_kg);
1480
- const price = parseFloat(formData.price);
1481
- const priority = parseInt(formData.priority);
1482
- if (isNaN(maxWeight) || maxWeight <= 0) {
1483
- errors.push("น้ำหนักสูงสุดต้องเป็นตัวเลขมากกว่า 0");
1484
- }
1485
- if (isNaN(price) || price < 0) {
1486
- errors.push("ราคาต้องเป็นตัวเลขไม่ติดลบ");
1467
+ if (!formData.name.trim()) {
1468
+ errors.push("กรุณากรอกชื่อวัสดุ");
1487
1469
  }
1488
- if (isNaN(priority)) {
1489
- errors.push("ลำดับความสำคัญต้องเป็นตัวเลข");
1470
+ if (!formData.unit.trim()) {
1471
+ errors.push("กรุณากรอกหน่วย");
1490
1472
  }
1491
- let etaHoursMin;
1492
- let etaHoursMax;
1493
- let etaDaysMin;
1494
- let etaDaysMax;
1495
- if (usesHours) {
1496
- etaHoursMin = formData.eta_hours_min ? parseFloat(formData.eta_hours_min) : void 0;
1497
- etaHoursMax = formData.eta_hours_max ? parseFloat(formData.eta_hours_max) : void 0;
1498
- if (etaHoursMin === void 0 || etaHoursMax === void 0) {
1499
- errors.push("กรุณากรอก ETA เป็นชั่วโมงให้ครบ");
1500
- } else if (etaHoursMin > etaHoursMax) {
1501
- errors.push("ETA ชั่วโมงต่ำสุดต้องน้อยกว่าหรือเท่ากับสูงสุด");
1502
- }
1503
- } else {
1504
- etaDaysMin = formData.eta_days_min ? parseFloat(formData.eta_days_min) : void 0;
1505
- etaDaysMax = formData.eta_days_max ? parseFloat(formData.eta_days_max) : void 0;
1506
- if (etaDaysMin === void 0 || etaDaysMax === void 0) {
1507
- errors.push("กรุณากรอก ETA เป็นวันให้ครบ");
1508
- } else if (etaDaysMin > etaDaysMax) {
1509
- errors.push("ETA วันต่ำสุดต้องน้อยกว่าหรือเท่ากับสูงสุด");
1510
- }
1473
+ const costPerUnit = parseFloat(formData.cost_per_unit);
1474
+ if (isNaN(costPerUnit) || costPerUnit < 0) {
1475
+ errors.push("ต้นทุนต่อหน่วยต้องเป็นตัวเลขไม่ติดลบ");
1511
1476
  }
1512
1477
  if (errors.length > 0) {
1513
1478
  toast.error("ข้อมูลไม่ถูกต้อง", {
@@ -1518,23 +1483,16 @@ const ShippingRateModal = ({ rate, onClose }) => {
1518
1483
  setIsSaving(true);
1519
1484
  try {
1520
1485
  const payload = {
1521
- carrier_type: formData.carrier_type,
1522
- service_code: formData.service_code,
1523
- max_weight_kg: maxWeight,
1524
- price,
1486
+ name: formData.name.trim(),
1487
+ unit: formData.unit.trim(),
1488
+ cost_per_unit: costPerUnit,
1525
1489
  currency: formData.currency,
1526
- priority,
1490
+ category: formData.category || void 0,
1491
+ description: formData.description.trim() || void 0,
1527
1492
  active: formData.active
1528
1493
  };
1529
- if (usesHours) {
1530
- payload.eta_hours_min = etaHoursMin;
1531
- payload.eta_hours_max = etaHoursMax;
1532
- } else {
1533
- payload.eta_days_min = etaDaysMin;
1534
- payload.eta_days_max = etaDaysMax;
1535
- }
1536
- const url = rate ? `/admin/shipping-rates/${rate.id}` : "/admin/shipping-rates";
1537
- const method = rate ? "PUT" : "POST";
1494
+ const url = materialCost ? `/admin/material-costs/${materialCost.id}` : "/admin/material-costs";
1495
+ const method = materialCost ? "PUT" : "POST";
1538
1496
  const response = await fetch(url, {
1539
1497
  method,
1540
1498
  headers: { "Content-Type": "application/json" },
@@ -1543,7 +1501,7 @@ const ShippingRateModal = ({ rate, onClose }) => {
1543
1501
  });
1544
1502
  if (response.ok) {
1545
1503
  toast.success("สำเร็จ", {
1546
- description: rate ? "แก้ไขอัตราค่าขนส่งแล้ว" : "สร้างอัตราค่าขนส่งใหม่แล้ว"
1504
+ description: materialCost ? "แก้ไขรายการต้นทุนวัสดุแล้ว" : "สร้างรายการต้นทุนวัสดุใหม่แล้ว"
1547
1505
  });
1548
1506
  onClose();
1549
1507
  } else {
@@ -1559,151 +1517,93 @@ const ShippingRateModal = ({ rate, onClose }) => {
1559
1517
  }
1560
1518
  };
1561
1519
  return /* @__PURE__ */ jsx(Drawer, { open: true, onOpenChange: (open) => !open && onClose(), children: /* @__PURE__ */ jsxs(Drawer.Content, { children: [
1562
- /* @__PURE__ */ jsx(Drawer.Header, { children: /* @__PURE__ */ jsx(Drawer.Title, { children: rate ? "แก้ไขอัตราค่าขนส่ง" : "สร้างอัตราค่าขนส่งใหม่" }) }),
1520
+ /* @__PURE__ */ jsx(Drawer.Header, { children: /* @__PURE__ */ jsx(Drawer.Title, { children: materialCost ? "แก้ไขรายการต้นทุนวัสดุ" : "สร้างรายการต้นทุนวัสดุใหม่" }) }),
1563
1521
  /* @__PURE__ */ jsx(Drawer.Body, { children: /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit, className: "flex flex-col gap-y-4", children: [
1564
1522
  /* @__PURE__ */ jsxs("div", { children: [
1565
- /* @__PURE__ */ jsx(Label, { htmlFor: "carrier", children: "ประเภทขนส่ง *" }),
1566
- /* @__PURE__ */ jsxs(
1567
- Select,
1523
+ /* @__PURE__ */ jsx(Label, { htmlFor: "name", children: "ชื่อวัสดุ *" }),
1524
+ /* @__PURE__ */ jsx(
1525
+ Input,
1568
1526
  {
1569
- value: formData.carrier_type,
1570
- onValueChange: (value) => setFormData({ ...formData, carrier_type: value }),
1571
- children: [
1572
- /* @__PURE__ */ jsx(Select.Trigger, { children: /* @__PURE__ */ jsx(Select.Value, { placeholder: "เลือกประเภทขนส่ง" }) }),
1573
- /* @__PURE__ */ jsx(Select.Content, { children: CARRIER_TYPES$1.map((carrier) => /* @__PURE__ */ jsx(Select.Item, { value: carrier.value, children: carrier.label }, carrier.value)) })
1574
- ]
1527
+ id: "name",
1528
+ value: formData.name,
1529
+ onChange: (e) => setFormData({ ...formData, name: e.target.value }),
1530
+ placeholder: "เช่น พลาสติกกันกระแทก, กล่องกระดาษ",
1531
+ required: true
1575
1532
  }
1576
1533
  )
1577
1534
  ] }),
1578
1535
  /* @__PURE__ */ jsxs("div", { children: [
1579
- /* @__PURE__ */ jsx(Label, { htmlFor: "service", children: "บริการจัดส่ง *" }),
1536
+ /* @__PURE__ */ jsx(Label, { htmlFor: "category", children: "หมวดหมู่ (ไม่บังคับ)" }),
1580
1537
  /* @__PURE__ */ jsxs(
1581
1538
  Select,
1582
1539
  {
1583
- value: formData.service_code,
1584
- onValueChange: (value) => setFormData({
1585
- ...formData,
1586
- service_code: value,
1587
- eta_hours_min: "",
1588
- eta_hours_max: "",
1589
- eta_days_min: "",
1590
- eta_days_max: ""
1591
- }),
1540
+ value: formData.category || void 0,
1541
+ onValueChange: (value) => setFormData({ ...formData, category: value }),
1592
1542
  children: [
1593
- /* @__PURE__ */ jsx(Select.Trigger, { children: /* @__PURE__ */ jsx(Select.Value, { placeholder: "เลือกบริการ" }) }),
1594
- /* @__PURE__ */ jsx(Select.Content, { children: SERVICE_CODES$1.map((service) => /* @__PURE__ */ jsx(Select.Item, { value: service.value, children: service.label }, service.value)) })
1543
+ /* @__PURE__ */ jsx(Select.Trigger, { children: /* @__PURE__ */ jsx(Select.Value, { placeholder: "เลือกหมวดหมู่ (ไม่บังคับ)" }) }),
1544
+ /* @__PURE__ */ jsx(Select.Content, { children: CATEGORIES$1.map((category) => /* @__PURE__ */ jsx(Select.Item, { value: category.value, children: category.label }, category.value)) })
1595
1545
  ]
1596
1546
  }
1597
1547
  )
1598
1548
  ] }),
1599
- isMessenger && /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-ui-border-base bg-ui-bg-subtle p-3 text-sm space-y-1", children: [
1600
- /* @__PURE__ */ jsx("div", { className: "font-semibold text-ui-fg-base", children: "ฐานราคาตามระยะทาง (Messenger)" }),
1601
- /* @__PURE__ */ jsx("div", { className: "text-ui-fg-subtle", children: "ตั้งช่วงราคาตามระยะทาง เช่น 0-5, 5-20, 20-30, 30+ กม. สำหรับบริการส่งด่วน 3 ชม." }),
1602
- /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(Button, { asChild: true, size: "small", variant: "secondary", children: /* @__PURE__ */ jsxs("a", { href: "/base-pricing", target: "_blank", rel: "noreferrer", children: [
1603
- "จัดการฐานราคา",
1604
- /* @__PURE__ */ jsx(ExternalLink, { className: "ml-1 h-4 w-4" })
1605
- ] }) }) })
1606
- ] }),
1607
1549
  /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-x-4", children: [
1608
1550
  /* @__PURE__ */ jsxs("div", { children: [
1609
- /* @__PURE__ */ jsx(Label, { htmlFor: "maxWeight", children: "น้ำหนักสูงสุด (kg) *" }),
1551
+ /* @__PURE__ */ jsx(Label, { htmlFor: "unit", children: "หน่วย *" }),
1610
1552
  /* @__PURE__ */ jsx(
1611
1553
  Input,
1612
1554
  {
1613
- id: "maxWeight",
1614
- type: "number",
1615
- step: "0.1",
1616
- min: "0",
1617
- value: formData.max_weight_kg,
1618
- onChange: (e) => setFormData({ ...formData, max_weight_kg: e.target.value }),
1619
- placeholder: "เช่น 3",
1555
+ id: "unit",
1556
+ value: formData.unit,
1557
+ onChange: (e) => setFormData({ ...formData, unit: e.target.value }),
1558
+ placeholder: "เช่น ชิ้น, เมตร, กิโลกรัม",
1620
1559
  required: true
1621
1560
  }
1622
1561
  )
1623
1562
  ] }),
1624
1563
  /* @__PURE__ */ jsxs("div", { children: [
1625
- /* @__PURE__ */ jsx(Label, { htmlFor: "price", children: "ราคา (THB) *" }),
1564
+ /* @__PURE__ */ jsx(Label, { htmlFor: "costPerUnit", children: "ต้นทุนต่อหน่วย *" }),
1626
1565
  /* @__PURE__ */ jsx(
1627
1566
  Input,
1628
1567
  {
1629
- id: "price",
1568
+ id: "costPerUnit",
1630
1569
  type: "number",
1631
1570
  step: "0.01",
1632
- min: "0",
1633
- value: formData.price,
1634
- onChange: (e) => setFormData({ ...formData, price: e.target.value }),
1635
- placeholder: "เช่น 80",
1571
+ value: formData.cost_per_unit,
1572
+ onChange: (e) => setFormData({ ...formData, cost_per_unit: e.target.value }),
1573
+ placeholder: "0.00",
1636
1574
  required: true
1637
1575
  }
1638
1576
  )
1639
1577
  ] })
1640
1578
  ] }),
1641
1579
  /* @__PURE__ */ jsxs("div", { children: [
1642
- /* @__PURE__ */ jsx(Label, { htmlFor: "priority", children: "ลำดับความสำคัญ" }),
1580
+ /* @__PURE__ */ jsx(Label, { htmlFor: "currency", children: "สกุลเงิน" }),
1643
1581
  /* @__PURE__ */ jsx(
1644
1582
  Input,
1645
1583
  {
1646
- id: "priority",
1647
- type: "number",
1648
- value: formData.priority,
1649
- onChange: (e) => setFormData({ ...formData, priority: e.target.value }),
1650
- placeholder: "0"
1584
+ id: "currency",
1585
+ value: formData.currency,
1586
+ onChange: (e) => setFormData({ ...formData, currency: e.target.value }),
1587
+ placeholder: "THB"
1651
1588
  }
1652
- ),
1653
- /* @__PURE__ */ jsx("p", { className: "text-xs text-ui-fg-subtle mt-1", children: "ตัวเลขสูงกว่าจะถูกเสนอให้เลือกก่อน (ค่าเริ่มต้น 0)" })
1589
+ )
1654
1590
  ] }),
1655
1591
  /* @__PURE__ */ jsxs("div", { children: [
1656
- /* @__PURE__ */ jsxs(Label, { children: [
1657
- usesHours ? "ETA (ชั่วโมง)" : "ETA (วัน)",
1658
- " *"
1659
- ] }),
1660
- /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 gap-x-4 mt-2", children: usesHours ? /* @__PURE__ */ jsxs(Fragment, { children: [
1661
- /* @__PURE__ */ jsx(
1662
- Input,
1663
- {
1664
- type: "number",
1665
- placeholder: "ต่ำสุด",
1666
- value: formData.eta_hours_min,
1667
- onChange: (e) => setFormData({ ...formData, eta_hours_min: e.target.value }),
1668
- required: true
1669
- }
1670
- ),
1671
- /* @__PURE__ */ jsx(
1672
- Input,
1673
- {
1674
- type: "number",
1675
- placeholder: "สูงสุด",
1676
- value: formData.eta_hours_max,
1677
- onChange: (e) => setFormData({ ...formData, eta_hours_max: e.target.value }),
1678
- required: true
1679
- }
1680
- )
1681
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
1682
- /* @__PURE__ */ jsx(
1683
- Input,
1684
- {
1685
- type: "number",
1686
- placeholder: "ต่ำสุด",
1687
- value: formData.eta_days_min,
1688
- onChange: (e) => setFormData({ ...formData, eta_days_min: e.target.value }),
1689
- required: true
1690
- }
1691
- ),
1692
- /* @__PURE__ */ jsx(
1693
- Input,
1694
- {
1695
- type: "number",
1696
- placeholder: "สูงสุด",
1697
- value: formData.eta_days_max,
1698
- onChange: (e) => setFormData({ ...formData, eta_days_max: e.target.value }),
1699
- required: true
1700
- }
1701
- )
1702
- ] }) })
1703
- ] }),
1704
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-x-2", children: [
1592
+ /* @__PURE__ */ jsx(Label, { htmlFor: "description", children: "คำอธิบาย" }),
1705
1593
  /* @__PURE__ */ jsx(
1706
- Switch,
1594
+ Textarea,
1595
+ {
1596
+ id: "description",
1597
+ value: formData.description,
1598
+ onChange: (e) => setFormData({ ...formData, description: e.target.value }),
1599
+ placeholder: "คำอธิบายเพิ่มเติมเกี่ยวกับวัสดุนี้...",
1600
+ rows: 3
1601
+ }
1602
+ )
1603
+ ] }),
1604
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-x-2", children: [
1605
+ /* @__PURE__ */ jsx(
1606
+ Switch,
1707
1607
  {
1708
1608
  id: "active",
1709
1609
  checked: formData.active,
@@ -1713,168 +1613,202 @@ const ShippingRateModal = ({ rate, onClose }) => {
1713
1613
  /* @__PURE__ */ jsx(Label, { htmlFor: "active", children: "เปิดใช้งาน" })
1714
1614
  ] }),
1715
1615
  /* @__PURE__ */ jsxs(Drawer.Footer, { children: [
1716
- /* @__PURE__ */ jsx(Button, { type: "button", variant: "secondary", onClick: onClose, disabled: isSaving, children: "ยกเลิก" }),
1616
+ /* @__PURE__ */ jsx(
1617
+ Button,
1618
+ {
1619
+ type: "button",
1620
+ variant: "secondary",
1621
+ onClick: onClose,
1622
+ disabled: isSaving,
1623
+ children: "ยกเลิก"
1624
+ }
1625
+ ),
1717
1626
  /* @__PURE__ */ jsx(Button, { type: "submit", disabled: isSaving, children: isSaving ? "กำลังบันทึก..." : "บันทึก" })
1718
1627
  ] })
1719
1628
  ] }) })
1720
1629
  ] }) });
1721
1630
  };
1722
- const CARRIER_TYPES = [
1723
- { value: "ALL", label: "ทั้งหมด" },
1724
- { value: "COMPANY_FLEET", label: "บริษัทรถส่งของ (Messenger)" },
1725
- { value: "COMPANY_TRUCK", label: "รถบริษัท (Same Day)" },
1726
- { value: "PRIVATE_CARRIER", label: "ขนส่งเอกชน (EMS/Express)" }
1727
- ];
1728
- const SERVICE_CODES = [
1631
+ const CATEGORIES = [
1729
1632
  { value: "ALL", label: "ทั้งหมด" },
1730
- { value: "MESSENGER_3H", label: "ส่งด่วน 3 ชม." },
1731
- { value: "SAME_DAY", label: "ส่งด่วนภายในวัน" },
1732
- { value: "STANDARD_3_5D", label: "EMS 3-5 วัน" },
1733
- { value: "EXPRESS_1_2D", label: "Express 1-2 วัน" }
1633
+ { value: "PACKAGING", label: "วัสดุหีบห่อ" },
1634
+ { value: "PROTECTION", label: "วัสดุป้องกัน" },
1635
+ { value: "FILLING", label: "วัสดุเติมช่องว่าง" },
1636
+ { value: "TAPE", label: "เทป/กาว" },
1637
+ { value: "LABEL", label: "ฉลาก/สติกเกอร์" },
1638
+ { value: "OTHER", label: "อื่นๆ" }
1734
1639
  ];
1735
- const ShippingRatesTable = () => {
1736
- const [rates, setRates] = useState([]);
1737
- const [filteredRates, setFilteredRates] = useState([]);
1738
- const [carrierFilter, setCarrierFilter] = useState("ALL");
1739
- const [serviceFilter, setServiceFilter] = useState("ALL");
1640
+ const MaterialCostsTable = () => {
1641
+ const [materialCosts, setMaterialCosts] = useState([]);
1642
+ const [filteredMaterialCosts, setFilteredMaterialCosts] = useState([]);
1643
+ const [searchTerm, setSearchTerm] = useState("");
1644
+ const [categoryFilter, setCategoryFilter] = useState("ALL");
1740
1645
  const [isModalOpen, setIsModalOpen] = useState(false);
1741
- const [editingRate, setEditingRate] = useState(null);
1646
+ const [editingMaterialCost, setEditingMaterialCost] = useState(null);
1742
1647
  const [isLoading, setIsLoading] = useState(false);
1743
- const [deleteRateId, setDeleteRateId] = useState(null);
1648
+ const [deleteMaterialCostId, setDeleteMaterialCostId] = useState(null);
1649
+ const [deleteMaterialCostName, setDeleteMaterialCostName] = useState("");
1744
1650
  useEffect(() => {
1745
- fetchRates();
1651
+ fetchMaterialCosts();
1746
1652
  }, []);
1747
1653
  useEffect(() => {
1748
- let filtered = rates;
1749
- if (carrierFilter !== "ALL") {
1750
- filtered = filtered.filter((rate) => rate.carrier_type === carrierFilter);
1654
+ let filtered = materialCosts;
1655
+ if (categoryFilter !== "ALL") {
1656
+ filtered = filtered.filter((mc) => mc.category === categoryFilter);
1751
1657
  }
1752
- if (serviceFilter !== "ALL") {
1753
- filtered = filtered.filter((rate) => rate.service_code === serviceFilter);
1658
+ if (searchTerm) {
1659
+ filtered = filtered.filter(
1660
+ (mc) => mc.name.toLowerCase().includes(searchTerm.toLowerCase())
1661
+ );
1754
1662
  }
1755
- setFilteredRates(filtered);
1756
- }, [carrierFilter, serviceFilter, rates]);
1757
- const fetchRates = async () => {
1663
+ setFilteredMaterialCosts(filtered);
1664
+ }, [searchTerm, categoryFilter, materialCosts]);
1665
+ const fetchMaterialCosts = async () => {
1758
1666
  setIsLoading(true);
1759
1667
  try {
1760
- const response = await fetch("/admin/shipping-rates", {
1668
+ const response = await fetch("/admin/material-costs", {
1761
1669
  credentials: "include"
1762
1670
  });
1763
1671
  const data = await response.json();
1764
- setRates(data.rates || []);
1672
+ setMaterialCosts(data.material_costs || []);
1765
1673
  } catch (error) {
1766
1674
  toast.error("ข้อผิดพลาด", {
1767
- description: "ไม่สามารถโหลดข้อมูลอัตราค่าขนส่งได้"
1675
+ description: "ไม่สามารถโหลดข้อมูลต้นทุนวัสดุได้"
1768
1676
  });
1769
1677
  } finally {
1770
1678
  setIsLoading(false);
1771
1679
  }
1772
1680
  };
1773
- const handleDelete = async () => {
1774
- if (!deleteRateId) return;
1681
+ const handleToggleActive = async (materialCost) => {
1775
1682
  try {
1776
- const response = await fetch(`/admin/shipping-rates/${deleteRateId}`, {
1777
- method: "DELETE",
1778
- credentials: "include"
1683
+ const response = await fetch(`/admin/material-costs/${materialCost.id}`, {
1684
+ method: "PUT",
1685
+ headers: { "Content-Type": "application/json" },
1686
+ credentials: "include",
1687
+ body: JSON.stringify({ ...materialCost, active: !materialCost.active })
1779
1688
  });
1780
1689
  if (response.ok) {
1781
1690
  toast.success("สำเร็จ", {
1782
- description: "ลบอัตราค่าขนส่งแล้ว"
1691
+ description: `${materialCost.active ? "ปิดใช้งาน" : "เปิดใช้งาน"}วัสดุ ${materialCost.name} แล้ว`
1783
1692
  });
1784
- fetchRates();
1693
+ fetchMaterialCosts();
1694
+ } else {
1695
+ throw new Error("Failed to update");
1696
+ }
1697
+ } catch (error) {
1698
+ toast.error("ข้อผิดพลาด", {
1699
+ description: "ไม่สามารถอัพเดทสถานะได้"
1700
+ });
1701
+ }
1702
+ };
1703
+ const handleDelete = async () => {
1704
+ if (!deleteMaterialCostId) return;
1705
+ try {
1706
+ const response = await fetch(
1707
+ `/admin/material-costs/${deleteMaterialCostId}`,
1708
+ {
1709
+ method: "DELETE",
1710
+ credentials: "include"
1711
+ }
1712
+ );
1713
+ if (response.ok) {
1714
+ toast.success("สำเร็จ", {
1715
+ description: `ลบวัสดุ ${deleteMaterialCostName} แล้ว`
1716
+ });
1717
+ fetchMaterialCosts();
1785
1718
  } else {
1786
1719
  throw new Error("Failed to delete");
1787
1720
  }
1788
1721
  } catch (error) {
1789
1722
  toast.error("ข้อผิดพลาด", {
1790
- description: "ไม่สามารถลบอัตราค่าขนส่งได้"
1723
+ description: "ไม่สามารถลบวัสดุได้"
1791
1724
  });
1792
1725
  } finally {
1793
- setDeleteRateId(null);
1726
+ setDeleteMaterialCostId(null);
1727
+ setDeleteMaterialCostName("");
1794
1728
  }
1795
1729
  };
1796
- const handleEdit = (rate) => {
1797
- setEditingRate(rate);
1730
+ const handleEdit = (materialCost) => {
1731
+ setEditingMaterialCost(materialCost);
1798
1732
  setIsModalOpen(true);
1799
1733
  };
1800
1734
  const handleCreateNew = () => {
1801
- setEditingRate(null);
1735
+ setEditingMaterialCost(null);
1802
1736
  setIsModalOpen(true);
1803
1737
  };
1804
1738
  const handleModalClose = () => {
1805
1739
  setIsModalOpen(false);
1806
- setEditingRate(null);
1807
- fetchRates();
1740
+ setEditingMaterialCost(null);
1741
+ fetchMaterialCosts();
1808
1742
  };
1809
- const getCarrierLabel = (type) => {
1810
- var _a;
1811
- return ((_a = CARRIER_TYPES.find((c) => c.value === type)) == null ? void 0 : _a.label) || type;
1743
+ const openDeletePrompt = (id, name) => {
1744
+ setDeleteMaterialCostId(id);
1745
+ setDeleteMaterialCostName(name);
1812
1746
  };
1813
- const getServiceLabel = (code) => {
1747
+ const getCategoryLabel = (category) => {
1814
1748
  var _a;
1815
- return ((_a = SERVICE_CODES.find((s) => s.value === code)) == null ? void 0 : _a.label) || code;
1816
- };
1817
- const formatETA = (rate) => {
1818
- if (rate.eta_hours_min !== null && rate.eta_hours_min !== void 0 && rate.eta_hours_max !== null && rate.eta_hours_max !== void 0) {
1819
- return `${rate.eta_hours_min}-${rate.eta_hours_max} ชม.`;
1820
- }
1821
- if (rate.eta_days_min !== null && rate.eta_days_min !== void 0 && rate.eta_days_max !== null && rate.eta_days_max !== void 0) {
1822
- return `${rate.eta_days_min}-${rate.eta_days_max} วัน`;
1823
- }
1824
- return "-";
1749
+ if (!category) return "-";
1750
+ return ((_a = CATEGORIES.find((c) => c.value === category)) == null ? void 0 : _a.label) || category;
1825
1751
  };
1826
1752
  return /* @__PURE__ */ jsxs(Fragment, { children: [
1827
1753
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-y-4", children: [
1828
1754
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-x-4", children: [
1829
- /* @__PURE__ */ jsxs("div", { className: "flex gap-x-4 items-end", children: [
1830
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-y-1", children: [
1831
- /* @__PURE__ */ jsx("label", { className: "text-sm font-medium text-ui-fg-subtle", children: "ประเภทขนส่ง" }),
1832
- /* @__PURE__ */ jsxs(Select, { value: carrierFilter, onValueChange: setCarrierFilter, children: [
1833
- /* @__PURE__ */ jsx(Select.Trigger, { children: /* @__PURE__ */ jsx(Select.Value, { placeholder: "ทั้งหมด" }) }),
1834
- /* @__PURE__ */ jsx(Select.Content, { children: CARRIER_TYPES.map((type) => /* @__PURE__ */ jsx(Select.Item, { value: type.value, children: type.label }, type.value)) })
1835
- ] })
1836
- ] }),
1837
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-y-1", children: [
1838
- /* @__PURE__ */ jsx("label", { className: "text-sm font-medium text-ui-fg-subtle", children: "บริการจัดส่ง" }),
1839
- /* @__PURE__ */ jsxs(Select, { value: serviceFilter, onValueChange: setServiceFilter, children: [
1840
- /* @__PURE__ */ jsx(Select.Trigger, { children: /* @__PURE__ */ jsx(Select.Value, { placeholder: "ทั้งหมด" }) }),
1841
- /* @__PURE__ */ jsx(Select.Content, { children: SERVICE_CODES.map((service) => /* @__PURE__ */ jsx(Select.Item, { value: service.value, children: service.label }, service.value)) })
1842
- ] })
1755
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-x-4 flex-1", children: [
1756
+ /* @__PURE__ */ jsx(
1757
+ Input,
1758
+ {
1759
+ type: "search",
1760
+ placeholder: "ค้นหาชื่อวัสดุ...",
1761
+ value: searchTerm,
1762
+ onChange: (e) => setSearchTerm(e.target.value),
1763
+ className: "max-w-md"
1764
+ }
1765
+ ),
1766
+ /* @__PURE__ */ jsxs(Select, { value: categoryFilter, onValueChange: setCategoryFilter, children: [
1767
+ /* @__PURE__ */ jsx(Select.Trigger, { children: /* @__PURE__ */ jsx(Select.Value, { placeholder: "หมวดหมู่" }) }),
1768
+ /* @__PURE__ */ jsx(Select.Content, { children: CATEGORIES.map((category) => /* @__PURE__ */ jsx(Select.Item, { value: category.value, children: category.label }, category.value)) })
1843
1769
  ] })
1844
1770
  ] }),
1845
1771
  /* @__PURE__ */ jsxs(Button, { onClick: handleCreateNew, children: [
1846
1772
  /* @__PURE__ */ jsx(Plus, {}),
1847
- "สร้างอัตราใหม่"
1773
+ "สร้างรายการใหม่"
1848
1774
  ] })
1849
1775
  ] }),
1850
1776
  /* @__PURE__ */ jsxs(Table, { children: [
1851
1777
  /* @__PURE__ */ jsx(Table.Header, { children: /* @__PURE__ */ jsxs(Table.Row, { children: [
1852
- /* @__PURE__ */ jsx(Table.HeaderCell, { children: "ประเภทขนส่ง" }),
1853
- /* @__PURE__ */ jsx(Table.HeaderCell, { children: "บริการ" }),
1854
- /* @__PURE__ */ jsx(Table.HeaderCell, { children: "น้ำหนัก ≤ (kg)" }),
1855
- /* @__PURE__ */ jsx(Table.HeaderCell, { children: "ราคา (THB)" }),
1856
- /* @__PURE__ */ jsx(Table.HeaderCell, { children: "ETA" }),
1778
+ /* @__PURE__ */ jsx(Table.HeaderCell, { children: "ชื่อ" }),
1779
+ /* @__PURE__ */ jsx(Table.HeaderCell, { children: "หมวดหมู่" }),
1780
+ /* @__PURE__ */ jsx(Table.HeaderCell, { children: "หน่วย" }),
1781
+ /* @__PURE__ */ jsx(Table.HeaderCell, { children: "ต้นทุนต่อหน่วย" }),
1782
+ /* @__PURE__ */ jsx(Table.HeaderCell, { children: "คำอธิบาย" }),
1857
1783
  /* @__PURE__ */ jsx(Table.HeaderCell, { children: "สถานะ" }),
1858
1784
  /* @__PURE__ */ jsx(Table.HeaderCell, { className: "text-right", children: "การจัดการ" })
1859
1785
  ] }) }),
1860
- /* @__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: [
1861
- /* @__PURE__ */ jsx(Table.Cell, { children: getCarrierLabel(rate.carrier_type) }),
1862
- /* @__PURE__ */ jsx(Table.Cell, { children: getServiceLabel(rate.service_code) }),
1786
+ /* @__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: [
1787
+ /* @__PURE__ */ jsx(Table.Cell, { children: materialCost.name }),
1788
+ /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(Badge, { color: "blue", children: getCategoryLabel(materialCost.category) }) }),
1789
+ /* @__PURE__ */ jsx(Table.Cell, { children: materialCost.unit }),
1863
1790
  /* @__PURE__ */ jsxs(Table.Cell, { children: [
1864
- "≤ ",
1865
- rate.max_weight_kg,
1866
- " kg"
1791
+ materialCost.cost_per_unit.toFixed(2),
1792
+ " ",
1793
+ materialCost.currency
1867
1794
  ] }),
1868
- /* @__PURE__ */ jsx(Table.Cell, { children: rate.price.toFixed(2) }),
1869
- /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(Badge, { color: "blue", children: formatETA(rate) }) }),
1870
- /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(Badge, { color: rate.active ? "green" : "grey", children: rate.active ? "เปิดใช้งาน" : "ปิดใช้งาน" }) }),
1795
+ /* @__PURE__ */ jsx(Table.Cell, { className: "max-w-xs truncate", children: materialCost.description || "-" }),
1796
+ /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(
1797
+ Badge,
1798
+ {
1799
+ color: materialCost.active ? "green" : "grey",
1800
+ className: "cursor-pointer",
1801
+ onClick: () => handleToggleActive(materialCost),
1802
+ children: materialCost.active ? "เปิดใช้งาน" : "ปิดใช้งาน"
1803
+ }
1804
+ ) }),
1871
1805
  /* @__PURE__ */ jsx(Table.Cell, { className: "text-right", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-x-2", children: [
1872
1806
  /* @__PURE__ */ jsx(
1873
1807
  Button,
1874
1808
  {
1875
1809
  variant: "transparent",
1876
1810
  size: "small",
1877
- onClick: () => handleEdit(rate),
1811
+ onClick: () => handleEdit(materialCost),
1878
1812
  children: /* @__PURE__ */ jsx(Edit, {})
1879
1813
  }
1880
1814
  ),
@@ -1883,25 +1817,38 @@ const ShippingRatesTable = () => {
1883
1817
  {
1884
1818
  variant: "transparent",
1885
1819
  size: "small",
1886
- onClick: () => setDeleteRateId(rate.id),
1820
+ onClick: () => openDeletePrompt(materialCost.id, materialCost.name),
1887
1821
  children: /* @__PURE__ */ jsx(Trash, {})
1888
1822
  }
1889
1823
  )
1890
1824
  ] }) })
1891
- ] }, rate.id)) })
1825
+ ] }, materialCost.id)) })
1892
1826
  ] })
1893
1827
  ] }),
1894
- isModalOpen && /* @__PURE__ */ jsx(ShippingRateModal, { rate: editingRate, onClose: handleModalClose }),
1828
+ isModalOpen && /* @__PURE__ */ jsx(
1829
+ MaterialCostModal,
1830
+ {
1831
+ materialCost: editingMaterialCost,
1832
+ onClose: handleModalClose
1833
+ }
1834
+ ),
1895
1835
  /* @__PURE__ */ jsx(
1896
1836
  Prompt,
1897
1837
  {
1898
1838
  variant: "confirmation",
1899
- open: !!deleteRateId,
1900
- onOpenChange: () => setDeleteRateId(null),
1839
+ open: !!deleteMaterialCostId,
1840
+ onOpenChange: () => {
1841
+ setDeleteMaterialCostId(null);
1842
+ setDeleteMaterialCostName("");
1843
+ },
1901
1844
  children: /* @__PURE__ */ jsxs(Prompt.Content, { children: [
1902
1845
  /* @__PURE__ */ jsxs(Prompt.Header, { children: [
1903
- /* @__PURE__ */ jsx(Prompt.Title, { children: "ลบอัตราค่าขนส่ง" }),
1904
- /* @__PURE__ */ jsx(Prompt.Description, { children: "คุณต้องการลบอัตราค่าขนส่งนี้ใช่หรือไม่? การดำเนินการนี้ไม่สามารถย้อนกลับได้" })
1846
+ /* @__PURE__ */ jsx(Prompt.Title, { children: "ลบรายการต้นทุนวัสดุ" }),
1847
+ /* @__PURE__ */ jsxs(Prompt.Description, { children: [
1848
+ 'คุณต้องการลบวัสดุ "',
1849
+ deleteMaterialCostName,
1850
+ '" ใช่หรือไม่? การดำเนินการนี้ไม่สามารถย้อนกลับได้'
1851
+ ] })
1905
1852
  ] }),
1906
1853
  /* @__PURE__ */ jsxs(Prompt.Footer, { children: [
1907
1854
  /* @__PURE__ */ jsx(Prompt.Cancel, { children: "ยกเลิก" }),
@@ -1912,26 +1859,59 @@ const ShippingRatesTable = () => {
1912
1859
  )
1913
1860
  ] });
1914
1861
  };
1915
- const CompanyTruckBaseRateModal = ({
1916
- rate,
1917
- onClose
1918
- }) => {
1919
- const [formData, setFormData] = useState({
1920
- min_distance_km: "0",
1921
- max_distance_km: "",
1862
+ const MaterialCostsPage = () => {
1863
+ return /* @__PURE__ */ jsx(Container, { children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-y-4", children: [
1864
+ /* @__PURE__ */ jsx("div", { className: "flex items-center justify-between", children: /* @__PURE__ */ jsx(Heading, { level: "h1", children: "ต้นทุนวัสดุ" }) }),
1865
+ /* @__PURE__ */ jsx(MaterialCostsTable, {})
1866
+ ] }) });
1867
+ };
1868
+ const config$1 = defineRouteConfig({
1869
+ icon: CurrencyDollarSolid,
1870
+ label: "ต้นทุนวัสดุ"
1871
+ });
1872
+ const CARRIER_TYPES$1 = [
1873
+ { value: "COMPANY_FLEET", label: "บริษัทรถส่งของ (Messenger)" },
1874
+ { value: "COMPANY_TRUCK", label: "รถบริษัท (Same Day)" },
1875
+ { value: "PRIVATE_CARRIER", label: "ขนส่งเอกชน (EMS/Express)" }
1876
+ ];
1877
+ const SERVICE_CODES$1 = [
1878
+ { value: "MESSENGER_3H", label: "ส่งด่วน 3 ชม.", usesHours: true },
1879
+ { value: "SAME_DAY", label: "ส่งด่วนภายในวัน", usesHours: true },
1880
+ { value: "STANDARD_3_5D", label: "EMS 3-5 วัน", usesHours: false },
1881
+ { value: "EXPRESS_1_2D", label: "Express 1-2 วัน", usesHours: false }
1882
+ ];
1883
+ const ShippingRateModal = ({ rate, onClose }) => {
1884
+ const [formData, setFormData] = useState({
1885
+ carrier_type: "COMPANY_FLEET",
1886
+ service_code: "MESSENGER_3H",
1887
+ max_weight_kg: "",
1922
1888
  price: "",
1889
+ currency: "THB",
1923
1890
  priority: "0",
1891
+ eta_hours_min: "",
1892
+ eta_hours_max: "",
1893
+ eta_days_min: "",
1894
+ eta_days_max: "",
1924
1895
  active: true
1925
1896
  });
1926
1897
  const [isSaving, setIsSaving] = useState(false);
1898
+ const selectedService = SERVICE_CODES$1.find((s) => s.value === formData.service_code);
1899
+ const usesHours = (selectedService == null ? void 0 : selectedService.usesHours) ?? false;
1900
+ const isMessenger = formData.carrier_type === "COMPANY_FLEET" && formData.service_code === "MESSENGER_3H";
1927
1901
  useEffect(() => {
1928
- var _a, _b;
1902
+ var _a, _b, _c, _d;
1929
1903
  if (rate) {
1930
1904
  setFormData({
1931
- min_distance_km: ((_a = rate.min_distance_km) == null ? void 0 : _a.toString()) ?? "0",
1932
- max_distance_km: ((_b = rate.max_distance_km) == null ? void 0 : _b.toString()) || "",
1905
+ carrier_type: rate.carrier_type,
1906
+ service_code: rate.service_code,
1907
+ max_weight_kg: rate.max_weight_kg.toString(),
1933
1908
  price: rate.price.toString(),
1909
+ currency: rate.currency,
1934
1910
  priority: (rate.priority ?? 0).toString(),
1911
+ eta_hours_min: ((_a = rate.eta_hours_min) == null ? void 0 : _a.toString()) || "",
1912
+ eta_hours_max: ((_b = rate.eta_hours_max) == null ? void 0 : _b.toString()) || "",
1913
+ eta_days_min: ((_c = rate.eta_days_min) == null ? void 0 : _c.toString()) || "",
1914
+ eta_days_max: ((_d = rate.eta_days_max) == null ? void 0 : _d.toString()) || "",
1935
1915
  active: rate.active
1936
1916
  });
1937
1917
  }
@@ -1939,16 +1919,11 @@ const CompanyTruckBaseRateModal = ({
1939
1919
  const handleSubmit = async (e) => {
1940
1920
  e.preventDefault();
1941
1921
  const errors = [];
1942
- const minDistance = parseFloat(formData.min_distance_km || "0");
1943
- const hasMaxDistance = formData.max_distance_km !== "";
1944
- const maxDistance = hasMaxDistance ? parseFloat(formData.max_distance_km) : void 0;
1922
+ const maxWeight = parseFloat(formData.max_weight_kg);
1945
1923
  const price = parseFloat(formData.price);
1946
- const priority = parseInt(formData.priority || "0", 10);
1947
- if (isNaN(minDistance) || minDistance < 0) {
1948
- errors.push("ระยะทางเริ่มต้นต้องไม่ติดลบ");
1949
- }
1950
- if (hasMaxDistance && (maxDistance === void 0 || isNaN(maxDistance) || (maxDistance ?? 0) < minDistance)) {
1951
- errors.push("ระยะทางสูงสุดต้องมากกว่าหรือเท่ากับระยะทางเริ่มต้น");
1924
+ const priority = parseInt(formData.priority);
1925
+ if (isNaN(maxWeight) || maxWeight <= 0) {
1926
+ errors.push("น้ำหนักสูงสุดต้องเป็นตัวเลขมากกว่า 0");
1952
1927
  }
1953
1928
  if (isNaN(price) || price < 0) {
1954
1929
  errors.push("ราคาต้องเป็นตัวเลขไม่ติดลบ");
@@ -1956,6 +1931,27 @@ const CompanyTruckBaseRateModal = ({
1956
1931
  if (isNaN(priority)) {
1957
1932
  errors.push("ลำดับความสำคัญต้องเป็นตัวเลข");
1958
1933
  }
1934
+ let etaHoursMin;
1935
+ let etaHoursMax;
1936
+ let etaDaysMin;
1937
+ let etaDaysMax;
1938
+ if (usesHours) {
1939
+ etaHoursMin = formData.eta_hours_min ? parseFloat(formData.eta_hours_min) : void 0;
1940
+ etaHoursMax = formData.eta_hours_max ? parseFloat(formData.eta_hours_max) : void 0;
1941
+ if (etaHoursMin === void 0 || etaHoursMax === void 0) {
1942
+ errors.push("กรุณากรอก ETA เป็นชั่วโมงให้ครบ");
1943
+ } else if (etaHoursMin > etaHoursMax) {
1944
+ errors.push("ETA ชั่วโมงต่ำสุดต้องน้อยกว่าหรือเท่ากับสูงสุด");
1945
+ }
1946
+ } else {
1947
+ etaDaysMin = formData.eta_days_min ? parseFloat(formData.eta_days_min) : void 0;
1948
+ etaDaysMax = formData.eta_days_max ? parseFloat(formData.eta_days_max) : void 0;
1949
+ if (etaDaysMin === void 0 || etaDaysMax === void 0) {
1950
+ errors.push("กรุณากรอก ETA เป็นวันให้ครบ");
1951
+ } else if (etaDaysMin > etaDaysMax) {
1952
+ errors.push("ETA วันต่ำสุดต้องน้อยกว่าหรือเท่ากับสูงสุด");
1953
+ }
1954
+ }
1959
1955
  if (errors.length > 0) {
1960
1956
  toast.error("ข้อมูลไม่ถูกต้อง", {
1961
1957
  description: errors.join(", ")
@@ -1965,17 +1961,22 @@ const CompanyTruckBaseRateModal = ({
1965
1961
  setIsSaving(true);
1966
1962
  try {
1967
1963
  const payload = {
1968
- carrier_type: "COMPANY_TRUCK",
1969
- carrier: "COMPANY_TRUCK",
1970
- service_code: "SAME_DAY",
1971
- min_distance_km: minDistance,
1972
- max_distance_km: maxDistance === void 0 || isNaN(maxDistance) ? null : maxDistance,
1964
+ carrier_type: formData.carrier_type,
1965
+ service_code: formData.service_code,
1966
+ max_weight_kg: maxWeight,
1973
1967
  price,
1974
- currency: "THB",
1968
+ currency: formData.currency,
1975
1969
  priority,
1976
1970
  active: formData.active
1977
1971
  };
1978
- const url = rate ? `/admin/base-shipping-prices/${rate.id}` : "/admin/base-shipping-prices";
1972
+ if (usesHours) {
1973
+ payload.eta_hours_min = etaHoursMin;
1974
+ payload.eta_hours_max = etaHoursMax;
1975
+ } else {
1976
+ payload.eta_days_min = etaDaysMin;
1977
+ payload.eta_days_max = etaDaysMax;
1978
+ }
1979
+ const url = rate ? `/admin/shipping-rates/${rate.id}` : "/admin/shipping-rates";
1979
1980
  const method = rate ? "PUT" : "POST";
1980
1981
  const response = await fetch(url, {
1981
1982
  method,
@@ -1983,14 +1984,15 @@ const CompanyTruckBaseRateModal = ({
1983
1984
  credentials: "include",
1984
1985
  body: JSON.stringify(payload)
1985
1986
  });
1986
- if (!response.ok) {
1987
- const error = await response.json().catch(() => ({}));
1988
- throw new Error(error.message || "ไม่สามารถบันทึกข้อมูลได้");
1987
+ if (response.ok) {
1988
+ toast.success("สำเร็จ", {
1989
+ description: rate ? "แก้ไขอัตราค่าขนส่งแล้ว" : "สร้างอัตราค่าขนส่งใหม่แล้ว"
1990
+ });
1991
+ onClose();
1992
+ } else {
1993
+ const error = await response.json();
1994
+ throw new Error(error.message || "Failed to save");
1989
1995
  }
1990
- toast.success("สำเร็จ", {
1991
- description: rate ? "แก้ไขฐานราคาตามระยะทางแล้ว" : "สร้างฐานราคาตามระยะทางใหม่แล้ว"
1992
- });
1993
- onClose();
1994
1996
  } catch (error) {
1995
1997
  toast.error("ข้อผิดพลาด", {
1996
1998
  description: error instanceof Error ? error.message : "ไม่สามารถบันทึกข้อมูลได้"
@@ -2000,56 +2002,85 @@ const CompanyTruckBaseRateModal = ({
2000
2002
  }
2001
2003
  };
2002
2004
  return /* @__PURE__ */ jsx(Drawer, { open: true, onOpenChange: (open) => !open && onClose(), children: /* @__PURE__ */ jsxs(Drawer.Content, { children: [
2003
- /* @__PURE__ */ jsx(Drawer.Header, { children: /* @__PURE__ */ jsx(Drawer.Title, { children: rate ? "แก้ไขฐานราคาตามระยะทาง (รถบริษัท)" : "สร้างฐานราคาตามระยะทาง (รถบริษัท)" }) }),
2005
+ /* @__PURE__ */ jsx(Drawer.Header, { children: /* @__PURE__ */ jsx(Drawer.Title, { children: rate ? "แก้ไขอัตราค่าขนส่ง" : "สร้างอัตราค่าขนส่งใหม่" }) }),
2004
2006
  /* @__PURE__ */ jsx(Drawer.Body, { children: /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit, className: "flex flex-col gap-y-4", children: [
2007
+ /* @__PURE__ */ jsxs("div", { children: [
2008
+ /* @__PURE__ */ jsx(Label, { htmlFor: "carrier", children: "ประเภทขนส่ง *" }),
2009
+ /* @__PURE__ */ jsxs(
2010
+ Select,
2011
+ {
2012
+ value: formData.carrier_type,
2013
+ onValueChange: (value) => setFormData({ ...formData, carrier_type: value }),
2014
+ children: [
2015
+ /* @__PURE__ */ jsx(Select.Trigger, { children: /* @__PURE__ */ jsx(Select.Value, { placeholder: "เลือกประเภทขนส่ง" }) }),
2016
+ /* @__PURE__ */ jsx(Select.Content, { children: CARRIER_TYPES$1.map((carrier) => /* @__PURE__ */ jsx(Select.Item, { value: carrier.value, children: carrier.label }, carrier.value)) })
2017
+ ]
2018
+ }
2019
+ )
2020
+ ] }),
2021
+ /* @__PURE__ */ jsxs("div", { children: [
2022
+ /* @__PURE__ */ jsx(Label, { htmlFor: "service", children: "บริการจัดส่ง *" }),
2023
+ /* @__PURE__ */ jsxs(
2024
+ Select,
2025
+ {
2026
+ value: formData.service_code,
2027
+ onValueChange: (value) => setFormData({
2028
+ ...formData,
2029
+ service_code: value,
2030
+ eta_hours_min: "",
2031
+ eta_hours_max: "",
2032
+ eta_days_min: "",
2033
+ eta_days_max: ""
2034
+ }),
2035
+ children: [
2036
+ /* @__PURE__ */ jsx(Select.Trigger, { children: /* @__PURE__ */ jsx(Select.Value, { placeholder: "เลือกบริการ" }) }),
2037
+ /* @__PURE__ */ jsx(Select.Content, { children: SERVICE_CODES$1.map((service) => /* @__PURE__ */ jsx(Select.Item, { value: service.value, children: service.label }, service.value)) })
2038
+ ]
2039
+ }
2040
+ )
2041
+ ] }),
2042
+ isMessenger && /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-ui-border-base bg-ui-bg-subtle p-3 text-sm space-y-1", children: [
2043
+ /* @__PURE__ */ jsx("div", { className: "font-semibold text-ui-fg-base", children: "ฐานราคาตามระยะทาง (Messenger)" }),
2044
+ /* @__PURE__ */ jsx("div", { className: "text-ui-fg-subtle", children: "ตั้งช่วงราคาตามระยะทาง เช่น 0-5, 5-20, 20-30, 30+ กม. สำหรับบริการส่งด่วน 3 ชม." }),
2045
+ /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(Button, { asChild: true, size: "small", variant: "secondary", children: /* @__PURE__ */ jsxs("a", { href: "/base-pricing", target: "_blank", rel: "noreferrer", children: [
2046
+ "จัดการฐานราคา",
2047
+ /* @__PURE__ */ jsx(ExternalLink, { className: "ml-1 h-4 w-4" })
2048
+ ] }) }) })
2049
+ ] }),
2005
2050
  /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-x-4", children: [
2006
2051
  /* @__PURE__ */ jsxs("div", { children: [
2007
- /* @__PURE__ */ jsx(Label, { htmlFor: "min_distance_km", children: "ระยะทางเริ่มต้น (กม.) *" }),
2052
+ /* @__PURE__ */ jsx(Label, { htmlFor: "maxWeight", children: "น้ำหนักสูงสุด (kg) *" }),
2008
2053
  /* @__PURE__ */ jsx(
2009
2054
  Input,
2010
2055
  {
2011
- id: "min_distance_km",
2056
+ id: "maxWeight",
2012
2057
  type: "number",
2013
- min: "0",
2014
2058
  step: "0.1",
2015
- value: formData.min_distance_km,
2016
- onChange: (e) => setFormData({ ...formData, min_distance_km: e.target.value }),
2059
+ min: "0",
2060
+ value: formData.max_weight_kg,
2061
+ onChange: (e) => setFormData({ ...formData, max_weight_kg: e.target.value }),
2062
+ placeholder: "เช่น 3",
2017
2063
  required: true
2018
2064
  }
2019
2065
  )
2020
2066
  ] }),
2021
2067
  /* @__PURE__ */ jsxs("div", { children: [
2022
- /* @__PURE__ */ jsx(Label, { htmlFor: "max_distance_km", children: "ระยะทางสูงสุด (กม.)" }),
2068
+ /* @__PURE__ */ jsx(Label, { htmlFor: "price", children: "ราคา (THB) *" }),
2023
2069
  /* @__PURE__ */ jsx(
2024
2070
  Input,
2025
2071
  {
2026
- id: "max_distance_km",
2072
+ id: "price",
2027
2073
  type: "number",
2074
+ step: "0.01",
2028
2075
  min: "0",
2029
- step: "0.1",
2030
- placeholder: "ปล่อยว่างหากไม่มีที่สิ้นสุด",
2031
- value: formData.max_distance_km,
2032
- onChange: (e) => setFormData({ ...formData, max_distance_km: e.target.value })
2076
+ value: formData.price,
2077
+ onChange: (e) => setFormData({ ...formData, price: e.target.value }),
2078
+ placeholder: "เช่น 80",
2079
+ required: true
2033
2080
  }
2034
- ),
2035
- /* @__PURE__ */ jsx("p", { className: "text-xs text-ui-fg-subtle mt-1", children: "เว้นว่างเพื่อกำหนดช่วงราคาแบบ 30+ กม." })
2081
+ )
2036
2082
  ] })
2037
2083
  ] }),
2038
- /* @__PURE__ */ jsxs("div", { children: [
2039
- /* @__PURE__ */ jsx(Label, { htmlFor: "price", children: "ราคา (THB) *" }),
2040
- /* @__PURE__ */ jsx(
2041
- Input,
2042
- {
2043
- id: "price",
2044
- type: "number",
2045
- min: "0",
2046
- step: "0.01",
2047
- value: formData.price,
2048
- onChange: (e) => setFormData({ ...formData, price: e.target.value }),
2049
- required: true
2050
- }
2051
- )
2052
- ] }),
2053
2084
  /* @__PURE__ */ jsxs("div", { children: [
2054
2085
  /* @__PURE__ */ jsx(Label, { htmlFor: "priority", children: "ลำดับความสำคัญ" }),
2055
2086
  /* @__PURE__ */ jsx(
@@ -2062,7 +2093,56 @@ const CompanyTruckBaseRateModal = ({
2062
2093
  placeholder: "0"
2063
2094
  }
2064
2095
  ),
2065
- /* @__PURE__ */ jsx("p", { className: "text-xs text-ui-fg-subtle mt-1", children: "ใช้สำหรับจัดลำดับเมื่อมีช่วงราคาทับกัน (ค่าเริ่มต้น 0)" })
2096
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-ui-fg-subtle mt-1", children: "ตัวเลขสูงกว่าจะถูกเสนอให้เลือกก่อน (ค่าเริ่มต้น 0)" })
2097
+ ] }),
2098
+ /* @__PURE__ */ jsxs("div", { children: [
2099
+ /* @__PURE__ */ jsxs(Label, { children: [
2100
+ usesHours ? "ETA (ชั่วโมง)" : "ETA (วัน)",
2101
+ " *"
2102
+ ] }),
2103
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 gap-x-4 mt-2", children: usesHours ? /* @__PURE__ */ jsxs(Fragment, { children: [
2104
+ /* @__PURE__ */ jsx(
2105
+ Input,
2106
+ {
2107
+ type: "number",
2108
+ placeholder: "ต่ำสุด",
2109
+ value: formData.eta_hours_min,
2110
+ onChange: (e) => setFormData({ ...formData, eta_hours_min: e.target.value }),
2111
+ required: true
2112
+ }
2113
+ ),
2114
+ /* @__PURE__ */ jsx(
2115
+ Input,
2116
+ {
2117
+ type: "number",
2118
+ placeholder: "สูงสุด",
2119
+ value: formData.eta_hours_max,
2120
+ onChange: (e) => setFormData({ ...formData, eta_hours_max: e.target.value }),
2121
+ required: true
2122
+ }
2123
+ )
2124
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
2125
+ /* @__PURE__ */ jsx(
2126
+ Input,
2127
+ {
2128
+ type: "number",
2129
+ placeholder: "ต่ำสุด",
2130
+ value: formData.eta_days_min,
2131
+ onChange: (e) => setFormData({ ...formData, eta_days_min: e.target.value }),
2132
+ required: true
2133
+ }
2134
+ ),
2135
+ /* @__PURE__ */ jsx(
2136
+ Input,
2137
+ {
2138
+ type: "number",
2139
+ placeholder: "สูงสุด",
2140
+ value: formData.eta_days_max,
2141
+ onChange: (e) => setFormData({ ...formData, eta_days_max: e.target.value }),
2142
+ required: true
2143
+ }
2144
+ )
2145
+ ] }) })
2066
2146
  ] }),
2067
2147
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-x-2", children: [
2068
2148
  /* @__PURE__ */ jsx(
@@ -2076,163 +2156,195 @@ const CompanyTruckBaseRateModal = ({
2076
2156
  /* @__PURE__ */ jsx(Label, { htmlFor: "active", children: "เปิดใช้งาน" })
2077
2157
  ] }),
2078
2158
  /* @__PURE__ */ jsxs(Drawer.Footer, { children: [
2079
- /* @__PURE__ */ jsx(
2080
- Button,
2081
- {
2082
- type: "button",
2083
- variant: "secondary",
2084
- onClick: onClose,
2085
- disabled: isSaving,
2086
- children: "ยกเลิก"
2087
- }
2088
- ),
2159
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "secondary", onClick: onClose, disabled: isSaving, children: "ยกเลิก" }),
2089
2160
  /* @__PURE__ */ jsx(Button, { type: "submit", disabled: isSaving, children: isSaving ? "กำลังบันทึก..." : "บันทึก" })
2090
2161
  ] })
2091
2162
  ] }) })
2092
2163
  ] }) });
2093
2164
  };
2094
- const CompanyTruckBasePricingTable = () => {
2165
+ const CARRIER_TYPES = [
2166
+ { value: "ALL", label: "ทั้งหมด" },
2167
+ { value: "COMPANY_FLEET", label: "บริษัทรถส่งของ (Messenger)" },
2168
+ { value: "COMPANY_TRUCK", label: "รถบริษัท (Same Day)" },
2169
+ { value: "PRIVATE_CARRIER", label: "ขนส่งเอกชน (EMS/Express)" }
2170
+ ];
2171
+ const SERVICE_CODES = [
2172
+ { value: "ALL", label: "ทั้งหมด" },
2173
+ { value: "MESSENGER_3H", label: "ส่งด่วน 3 ชม." },
2174
+ { value: "SAME_DAY", label: "ส่งด่วนภายในวัน" },
2175
+ { value: "STANDARD_3_5D", label: "EMS 3-5 วัน" },
2176
+ { value: "EXPRESS_1_2D", label: "Express 1-2 วัน" }
2177
+ ];
2178
+ const ShippingRatesTable = () => {
2095
2179
  const [rates, setRates] = useState([]);
2096
- const [isLoading, setIsLoading] = useState(false);
2180
+ const [filteredRates, setFilteredRates] = useState([]);
2181
+ const [carrierFilter, setCarrierFilter] = useState("ALL");
2182
+ const [serviceFilter, setServiceFilter] = useState("ALL");
2097
2183
  const [isModalOpen, setIsModalOpen] = useState(false);
2098
2184
  const [editingRate, setEditingRate] = useState(null);
2099
- const [deleteId, setDeleteId] = useState(null);
2185
+ const [isLoading, setIsLoading] = useState(false);
2186
+ const [deleteRateId, setDeleteRateId] = useState(null);
2100
2187
  useEffect(() => {
2101
2188
  fetchRates();
2102
2189
  }, []);
2103
- const orderedRates = useMemo(() => {
2104
- return [...rates].sort((a, b) => {
2105
- if (a.min_distance_km !== b.min_distance_km) {
2106
- return a.min_distance_km - b.min_distance_km;
2107
- }
2108
- const aMax = a.max_distance_km ?? Number.POSITIVE_INFINITY;
2109
- const bMax = b.max_distance_km ?? Number.POSITIVE_INFINITY;
2110
- return aMax - bMax;
2111
- });
2112
- }, [rates]);
2190
+ useEffect(() => {
2191
+ let filtered = rates;
2192
+ if (carrierFilter !== "ALL") {
2193
+ filtered = filtered.filter((rate) => rate.carrier_type === carrierFilter);
2194
+ }
2195
+ if (serviceFilter !== "ALL") {
2196
+ filtered = filtered.filter((rate) => rate.service_code === serviceFilter);
2197
+ }
2198
+ setFilteredRates(filtered);
2199
+ }, [carrierFilter, serviceFilter, rates]);
2113
2200
  const fetchRates = async () => {
2114
2201
  setIsLoading(true);
2115
2202
  try {
2116
- const response = await fetch(
2117
- "/admin/base-shipping-prices?carrier_type=COMPANY_TRUCK&service_code=SAME_DAY",
2118
- {
2119
- credentials: "include"
2120
- }
2121
- );
2122
- if (!response.ok) {
2123
- throw new Error("failed_to_load_base_rates");
2124
- }
2203
+ const response = await fetch("/admin/shipping-rates", {
2204
+ credentials: "include"
2205
+ });
2125
2206
  const data = await response.json();
2126
- setRates(data.base_rates || []);
2207
+ setRates(data.rates || []);
2127
2208
  } catch (error) {
2128
2209
  toast.error("ข้อผิดพลาด", {
2129
- description: "ไม่สามารถโหลดฐานราคาตามระยะทางได้"
2210
+ description: "ไม่สามารถโหลดข้อมูลอัตราค่าขนส่งได้"
2130
2211
  });
2131
2212
  } finally {
2132
2213
  setIsLoading(false);
2133
2214
  }
2134
2215
  };
2135
2216
  const handleDelete = async () => {
2136
- if (!deleteId) return;
2217
+ if (!deleteRateId) return;
2137
2218
  try {
2138
- const response = await fetch(
2139
- `/admin/base-shipping-prices/${deleteId}`,
2140
- {
2141
- method: "DELETE",
2142
- credentials: "include"
2143
- }
2144
- );
2145
- if (!response.ok) {
2146
- throw new Error("failed_to_delete");
2147
- }
2148
- toast.success("สำเร็จ", {
2149
- description: "ลบฐานราคาสำเร็จ"
2219
+ const response = await fetch(`/admin/shipping-rates/${deleteRateId}`, {
2220
+ method: "DELETE",
2221
+ credentials: "include"
2150
2222
  });
2151
- fetchRates();
2223
+ if (response.ok) {
2224
+ toast.success("สำเร็จ", {
2225
+ description: "ลบอัตราค่าขนส่งแล้ว"
2226
+ });
2227
+ fetchRates();
2228
+ } else {
2229
+ throw new Error("Failed to delete");
2230
+ }
2152
2231
  } catch (error) {
2153
2232
  toast.error("ข้อผิดพลาด", {
2154
- description: "ไม่สามารถลบฐานราคาได้"
2233
+ description: "ไม่สามารถลบอัตราค่าขนส่งได้"
2155
2234
  });
2156
2235
  } finally {
2157
- setDeleteId(null);
2236
+ setDeleteRateId(null);
2158
2237
  }
2159
2238
  };
2239
+ const handleEdit = (rate) => {
2240
+ setEditingRate(rate);
2241
+ setIsModalOpen(true);
2242
+ };
2243
+ const handleCreateNew = () => {
2244
+ setEditingRate(null);
2245
+ setIsModalOpen(true);
2246
+ };
2160
2247
  const handleModalClose = () => {
2161
2248
  setIsModalOpen(false);
2162
2249
  setEditingRate(null);
2163
2250
  fetchRates();
2164
2251
  };
2165
- const formatDistanceRange = (rate) => {
2166
- const min = rate.min_distance_km ?? 0;
2167
- const max = rate.max_distance_km;
2168
- if (max === void 0 || max === null) {
2169
- return `${min}+ กม.`;
2252
+ const getCarrierLabel = (type) => {
2253
+ var _a;
2254
+ return ((_a = CARRIER_TYPES.find((c) => c.value === type)) == null ? void 0 : _a.label) || type;
2255
+ };
2256
+ const getServiceLabel = (code) => {
2257
+ var _a;
2258
+ return ((_a = SERVICE_CODES.find((s) => s.value === code)) == null ? void 0 : _a.label) || code;
2259
+ };
2260
+ const formatETA = (rate) => {
2261
+ if (rate.eta_hours_min !== null && rate.eta_hours_min !== void 0 && rate.eta_hours_max !== null && rate.eta_hours_max !== void 0) {
2262
+ return `${rate.eta_hours_min}-${rate.eta_hours_max} ชม.`;
2170
2263
  }
2171
- return `${min}-${max} กม.`;
2264
+ if (rate.eta_days_min !== null && rate.eta_days_min !== void 0 && rate.eta_days_max !== null && rate.eta_days_max !== void 0) {
2265
+ return `${rate.eta_days_min}-${rate.eta_days_max} วัน`;
2266
+ }
2267
+ return "-";
2172
2268
  };
2173
2269
  return /* @__PURE__ */ jsxs(Fragment, { children: [
2174
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-x-4", children: [
2175
- /* @__PURE__ */ jsxs("div", { children: [
2176
- /* @__PURE__ */ jsx("p", { className: "text-sm font-semibold", children: "ฐานราคาตามระยะทาง (รถบริษัท - Same Day)" }),
2177
- /* @__PURE__ */ jsx("p", { className: "text-ui-fg-subtle text-sm", children: "กำหนดราคาเริ่มต้นตามระยะทาง เช่น 0-10, 10-30, 30-50, 50+ กม." })
2270
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-y-4", children: [
2271
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-x-4", children: [
2272
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-x-4 items-end", children: [
2273
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-y-1", children: [
2274
+ /* @__PURE__ */ jsx("label", { className: "text-sm font-medium text-ui-fg-subtle", children: "ประเภทขนส่ง" }),
2275
+ /* @__PURE__ */ jsxs(Select, { value: carrierFilter, onValueChange: setCarrierFilter, children: [
2276
+ /* @__PURE__ */ jsx(Select.Trigger, { children: /* @__PURE__ */ jsx(Select.Value, { placeholder: "ทั้งหมด" }) }),
2277
+ /* @__PURE__ */ jsx(Select.Content, { children: CARRIER_TYPES.map((type) => /* @__PURE__ */ jsx(Select.Item, { value: type.value, children: type.label }, type.value)) })
2278
+ ] })
2279
+ ] }),
2280
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-y-1", children: [
2281
+ /* @__PURE__ */ jsx("label", { className: "text-sm font-medium text-ui-fg-subtle", children: "บริการจัดส่ง" }),
2282
+ /* @__PURE__ */ jsxs(Select, { value: serviceFilter, onValueChange: setServiceFilter, children: [
2283
+ /* @__PURE__ */ jsx(Select.Trigger, { children: /* @__PURE__ */ jsx(Select.Value, { placeholder: "ทั้งหมด" }) }),
2284
+ /* @__PURE__ */ jsx(Select.Content, { children: SERVICE_CODES.map((service) => /* @__PURE__ */ jsx(Select.Item, { value: service.value, children: service.label }, service.value)) })
2285
+ ] })
2286
+ ] })
2287
+ ] }),
2288
+ /* @__PURE__ */ jsxs(Button, { onClick: handleCreateNew, children: [
2289
+ /* @__PURE__ */ jsx(Plus, {}),
2290
+ "สร้างอัตราใหม่"
2291
+ ] })
2178
2292
  ] }),
2179
- /* @__PURE__ */ jsxs(Button, { onClick: () => setIsModalOpen(true), children: [
2180
- /* @__PURE__ */ jsx(Plus, {}),
2181
- "เพิ่มช่วงราคา"
2293
+ /* @__PURE__ */ jsxs(Table, { children: [
2294
+ /* @__PURE__ */ jsx(Table.Header, { children: /* @__PURE__ */ jsxs(Table.Row, { children: [
2295
+ /* @__PURE__ */ jsx(Table.HeaderCell, { children: "ประเภทขนส่ง" }),
2296
+ /* @__PURE__ */ jsx(Table.HeaderCell, { children: "บริการ" }),
2297
+ /* @__PURE__ */ jsx(Table.HeaderCell, { children: "น้ำหนัก ≤ (kg)" }),
2298
+ /* @__PURE__ */ jsx(Table.HeaderCell, { children: "ราคา (THB)" }),
2299
+ /* @__PURE__ */ jsx(Table.HeaderCell, { children: "ETA" }),
2300
+ /* @__PURE__ */ jsx(Table.HeaderCell, { children: "สถานะ" }),
2301
+ /* @__PURE__ */ jsx(Table.HeaderCell, { className: "text-right", children: "การจัดการ" })
2302
+ ] }) }),
2303
+ /* @__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: [
2304
+ /* @__PURE__ */ jsx(Table.Cell, { children: getCarrierLabel(rate.carrier_type) }),
2305
+ /* @__PURE__ */ jsx(Table.Cell, { children: getServiceLabel(rate.service_code) }),
2306
+ /* @__PURE__ */ jsxs(Table.Cell, { children: [
2307
+ "≤ ",
2308
+ rate.max_weight_kg,
2309
+ " kg"
2310
+ ] }),
2311
+ /* @__PURE__ */ jsx(Table.Cell, { children: rate.price.toFixed(2) }),
2312
+ /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(Badge, { color: "blue", children: formatETA(rate) }) }),
2313
+ /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(Badge, { color: rate.active ? "green" : "grey", children: rate.active ? "เปิดใช้งาน" : "ปิดใช้งาน" }) }),
2314
+ /* @__PURE__ */ jsx(Table.Cell, { className: "text-right", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-x-2", children: [
2315
+ /* @__PURE__ */ jsx(
2316
+ Button,
2317
+ {
2318
+ variant: "transparent",
2319
+ size: "small",
2320
+ onClick: () => handleEdit(rate),
2321
+ children: /* @__PURE__ */ jsx(Edit, {})
2322
+ }
2323
+ ),
2324
+ /* @__PURE__ */ jsx(
2325
+ Button,
2326
+ {
2327
+ variant: "transparent",
2328
+ size: "small",
2329
+ onClick: () => setDeleteRateId(rate.id),
2330
+ children: /* @__PURE__ */ jsx(Trash, {})
2331
+ }
2332
+ )
2333
+ ] }) })
2334
+ ] }, rate.id)) })
2182
2335
  ] })
2183
2336
  ] }),
2184
- /* @__PURE__ */ jsxs(Table, { className: "mt-4", children: [
2185
- /* @__PURE__ */ jsx(Table.Header, { children: /* @__PURE__ */ jsxs(Table.Row, { children: [
2186
- /* @__PURE__ */ jsx(Table.HeaderCell, { children: "ช่วงระยะทาง" }),
2187
- /* @__PURE__ */ jsx(Table.HeaderCell, { children: "ราคา (THB)" }),
2188
- /* @__PURE__ */ jsx(Table.HeaderCell, { children: "ลำดับ" }),
2189
- /* @__PURE__ */ jsx(Table.HeaderCell, { children: "สถานะ" }),
2190
- /* @__PURE__ */ jsx(Table.HeaderCell, { className: "text-right", children: "การจัดการ" })
2191
- ] }) }),
2192
- /* @__PURE__ */ jsx(Table.Body, { children: isLoading ? /* @__PURE__ */ jsx(Table.Row, { children: /* @__PURE__ */ jsx(Table.Cell, { colSpan: 5, className: "text-center", children: "กำลังโหลด..." }) }) : orderedRates.length === 0 ? /* @__PURE__ */ jsx(Table.Row, { children: /* @__PURE__ */ jsx(Table.Cell, { colSpan: 5, className: "text-center", children: "ยังไม่ได้ตั้งค่าฐานราคาตามระยะทาง (รถบริษัทจะใช้ราคาตามน้ำหนัก)" }) }) : orderedRates.map((rate) => /* @__PURE__ */ jsxs(Table.Row, { children: [
2193
- /* @__PURE__ */ jsx(Table.Cell, { children: formatDistanceRange(rate) }),
2194
- /* @__PURE__ */ jsxs(Table.Cell, { children: [
2195
- "฿",
2196
- rate.price.toFixed(2)
2197
- ] }),
2198
- /* @__PURE__ */ jsx(Table.Cell, { children: rate.priority ?? 0 }),
2199
- /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(Badge, { color: rate.active ? "green" : "grey", children: rate.active ? "เปิดใช้งาน" : "ปิดใช้งาน" }) }),
2200
- /* @__PURE__ */ jsx(Table.Cell, { className: "text-right", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-x-2", children: [
2201
- /* @__PURE__ */ jsx(
2202
- Button,
2203
- {
2204
- variant: "transparent",
2205
- size: "small",
2206
- onClick: () => {
2207
- setEditingRate(rate);
2208
- setIsModalOpen(true);
2209
- },
2210
- children: /* @__PURE__ */ jsx(Edit, {})
2211
- }
2212
- ),
2213
- /* @__PURE__ */ jsx(
2214
- Button,
2215
- {
2216
- variant: "transparent",
2217
- size: "small",
2218
- onClick: () => setDeleteId(rate.id),
2219
- children: /* @__PURE__ */ jsx(Trash, {})
2220
- }
2221
- )
2222
- ] }) })
2223
- ] }, rate.id)) })
2224
- ] }),
2225
- isModalOpen && /* @__PURE__ */ jsx(CompanyTruckBaseRateModal, { rate: editingRate, onClose: handleModalClose }),
2337
+ isModalOpen && /* @__PURE__ */ jsx(ShippingRateModal, { rate: editingRate, onClose: handleModalClose }),
2226
2338
  /* @__PURE__ */ jsx(
2227
2339
  Prompt,
2228
2340
  {
2229
2341
  variant: "confirmation",
2230
- open: !!deleteId,
2231
- onOpenChange: () => setDeleteId(null),
2342
+ open: !!deleteRateId,
2343
+ onOpenChange: () => setDeleteRateId(null),
2232
2344
  children: /* @__PURE__ */ jsxs(Prompt.Content, { children: [
2233
2345
  /* @__PURE__ */ jsxs(Prompt.Header, { children: [
2234
- /* @__PURE__ */ jsx(Prompt.Title, { children: "ลบฐานราคา" }),
2235
- /* @__PURE__ */ jsx(Prompt.Description, { children: "คุณต้องการลบช่วงราคานี้ใช่หรือไม่? การดำเนินการนี้ไม่สามารถย้อนกลับได้" })
2346
+ /* @__PURE__ */ jsx(Prompt.Title, { children: "ลบอัตราค่าขนส่ง" }),
2347
+ /* @__PURE__ */ jsx(Prompt.Description, { children: "คุณต้องการลบอัตราค่าขนส่งนี้ใช่หรือไม่? การดำเนินการนี้ไม่สามารถย้อนกลับได้" })
2236
2348
  ] }),
2237
2349
  /* @__PURE__ */ jsxs(Prompt.Footer, { children: [
2238
2350
  /* @__PURE__ */ jsx(Prompt.Cancel, { children: "ยกเลิก" }),
@@ -2243,71 +2355,49 @@ const CompanyTruckBasePricingTable = () => {
2243
2355
  )
2244
2356
  ] });
2245
2357
  };
2246
- const RatesPage = () => {
2247
- return /* @__PURE__ */ jsx(Container, { children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-y-8", children: [
2248
- /* @__PURE__ */ jsx("div", { className: "flex items-center justify-between", children: /* @__PURE__ */ jsx(Heading, { level: "h1", children: "อัตราค่าขนส่ง" }) }),
2249
- /* @__PURE__ */ jsx(ShippingRatesTable, {}),
2250
- /* @__PURE__ */ jsxs("div", { className: "border-t pt-4", children: [
2251
- /* @__PURE__ */ jsx(Heading, { level: "h2", className: "mb-2 text-lg", children: "ฐานราคาตามระยะทาง (Messenger 3H)" }),
2252
- /* @__PURE__ */ jsx(MessengerBasePricingTable, {})
2253
- ] }),
2254
- /* @__PURE__ */ jsxs("div", { className: "border-t pt-4", children: [
2255
- /* @__PURE__ */ jsx(Heading, { level: "h2", className: "mb-2 text-lg", children: "ฐานราคาตามระยะทาง (รถบริษัท - Same Day)" }),
2256
- /* @__PURE__ */ jsx(CompanyTruckBasePricingTable, {})
2257
- ] })
2258
- ] }) });
2259
- };
2260
- const config$1 = defineRouteConfig({
2261
- icon: HandTruck,
2262
- label: "อัตราค่าขนส่ง"
2263
- });
2264
- const CATEGORIES$1 = [
2265
- { value: "PACKAGING", label: "วัสดุหีบห่อ" },
2266
- { value: "PROTECTION", label: "วัสดุป้องกัน" },
2267
- { value: "FILLING", label: "วัสดุเติมช่องว่าง" },
2268
- { value: "TAPE", label: "เทป/กาว" },
2269
- { value: "LABEL", label: "ฉลาก/สติกเกอร์" },
2270
- { value: "OTHER", label: "อื่นๆ" }
2271
- ];
2272
- const MaterialCostModal = ({
2273
- materialCost,
2358
+ const CompanyTruckBaseRateModal = ({
2359
+ rate,
2274
2360
  onClose
2275
2361
  }) => {
2276
2362
  const [formData, setFormData] = useState({
2277
- name: "",
2278
- unit: "",
2279
- cost_per_unit: "",
2280
- currency: "THB",
2281
- category: "",
2282
- description: "",
2363
+ min_distance_km: "0",
2364
+ max_distance_km: "",
2365
+ price: "",
2366
+ priority: "0",
2283
2367
  active: true
2284
2368
  });
2285
2369
  const [isSaving, setIsSaving] = useState(false);
2286
2370
  useEffect(() => {
2287
- if (materialCost) {
2371
+ var _a, _b;
2372
+ if (rate) {
2288
2373
  setFormData({
2289
- name: materialCost.name,
2290
- unit: materialCost.unit,
2291
- cost_per_unit: materialCost.cost_per_unit.toString(),
2292
- currency: materialCost.currency,
2293
- category: materialCost.category || "",
2294
- description: materialCost.description || "",
2295
- active: materialCost.active
2374
+ min_distance_km: ((_a = rate.min_distance_km) == null ? void 0 : _a.toString()) ?? "0",
2375
+ max_distance_km: ((_b = rate.max_distance_km) == null ? void 0 : _b.toString()) || "",
2376
+ price: rate.price.toString(),
2377
+ priority: (rate.priority ?? 0).toString(),
2378
+ active: rate.active
2296
2379
  });
2297
2380
  }
2298
- }, [materialCost]);
2381
+ }, [rate]);
2299
2382
  const handleSubmit = async (e) => {
2300
2383
  e.preventDefault();
2301
2384
  const errors = [];
2302
- if (!formData.name.trim()) {
2303
- errors.push("กรุณากรอกชื่อวัสดุ");
2385
+ const minDistance = parseFloat(formData.min_distance_km || "0");
2386
+ const hasMaxDistance = formData.max_distance_km !== "";
2387
+ const maxDistance = hasMaxDistance ? parseFloat(formData.max_distance_km) : void 0;
2388
+ const price = parseFloat(formData.price);
2389
+ const priority = parseInt(formData.priority || "0", 10);
2390
+ if (isNaN(minDistance) || minDistance < 0) {
2391
+ errors.push("ระยะทางเริ่มต้นต้องไม่ติดลบ");
2304
2392
  }
2305
- if (!formData.unit.trim()) {
2306
- errors.push("กรุณากรอกหน่วย");
2393
+ if (hasMaxDistance && (maxDistance === void 0 || isNaN(maxDistance) || (maxDistance ?? 0) < minDistance)) {
2394
+ errors.push("ระยะทางสูงสุดต้องมากกว่าหรือเท่ากับระยะทางเริ่มต้น");
2307
2395
  }
2308
- const costPerUnit = parseFloat(formData.cost_per_unit);
2309
- if (isNaN(costPerUnit) || costPerUnit < 0) {
2310
- errors.push("ต้นทุนต่อหน่วยต้องเป็นตัวเลขไม่ติดลบ");
2396
+ if (isNaN(price) || price < 0) {
2397
+ errors.push("ราคาต้องเป็นตัวเลขไม่ติดลบ");
2398
+ }
2399
+ if (isNaN(priority)) {
2400
+ errors.push("ลำดับความสำคัญต้องเป็นตัวเลข");
2311
2401
  }
2312
2402
  if (errors.length > 0) {
2313
2403
  toast.error("ข้อมูลไม่ถูกต้อง", {
@@ -2318,31 +2408,32 @@ const MaterialCostModal = ({
2318
2408
  setIsSaving(true);
2319
2409
  try {
2320
2410
  const payload = {
2321
- name: formData.name.trim(),
2322
- unit: formData.unit.trim(),
2323
- cost_per_unit: costPerUnit,
2324
- currency: formData.currency,
2325
- category: formData.category || void 0,
2326
- description: formData.description.trim() || void 0,
2411
+ carrier_type: "COMPANY_TRUCK",
2412
+ carrier: "COMPANY_TRUCK",
2413
+ service_code: "SAME_DAY",
2414
+ min_distance_km: minDistance,
2415
+ max_distance_km: maxDistance === void 0 || isNaN(maxDistance) ? null : maxDistance,
2416
+ price,
2417
+ currency: "THB",
2418
+ priority,
2327
2419
  active: formData.active
2328
2420
  };
2329
- const url = materialCost ? `/admin/material-costs/${materialCost.id}` : "/admin/material-costs";
2330
- const method = materialCost ? "PUT" : "POST";
2421
+ const url = rate ? `/admin/base-shipping-prices/${rate.id}` : "/admin/base-shipping-prices";
2422
+ const method = rate ? "PUT" : "POST";
2331
2423
  const response = await fetch(url, {
2332
2424
  method,
2333
2425
  headers: { "Content-Type": "application/json" },
2334
2426
  credentials: "include",
2335
2427
  body: JSON.stringify(payload)
2336
2428
  });
2337
- if (response.ok) {
2338
- toast.success("สำเร็จ", {
2339
- description: materialCost ? "แก้ไขรายการต้นทุนวัสดุแล้ว" : "สร้างรายการต้นทุนวัสดุใหม่แล้ว"
2340
- });
2341
- onClose();
2342
- } else {
2343
- const error = await response.json();
2344
- throw new Error(error.message || "Failed to save");
2429
+ if (!response.ok) {
2430
+ const error = await response.json().catch(() => ({}));
2431
+ throw new Error(error.message || "ไม่สามารถบันทึกข้อมูลได้");
2345
2432
  }
2433
+ toast.success("สำเร็จ", {
2434
+ description: rate ? "แก้ไขฐานราคาตามระยะทางแล้ว" : "สร้างฐานราคาตามระยะทางใหม่แล้ว"
2435
+ });
2436
+ onClose();
2346
2437
  } catch (error) {
2347
2438
  toast.error("ข้อผิดพลาด", {
2348
2439
  description: error instanceof Error ? error.message : "ไม่สามารถบันทึกข้อมูลได้"
@@ -2352,89 +2443,69 @@ const MaterialCostModal = ({
2352
2443
  }
2353
2444
  };
2354
2445
  return /* @__PURE__ */ jsx(Drawer, { open: true, onOpenChange: (open) => !open && onClose(), children: /* @__PURE__ */ jsxs(Drawer.Content, { children: [
2355
- /* @__PURE__ */ jsx(Drawer.Header, { children: /* @__PURE__ */ jsx(Drawer.Title, { children: materialCost ? "แก้ไขรายการต้นทุนวัสดุ" : "สร้างรายการต้นทุนวัสดุใหม่" }) }),
2446
+ /* @__PURE__ */ jsx(Drawer.Header, { children: /* @__PURE__ */ jsx(Drawer.Title, { children: rate ? "แก้ไขฐานราคาตามระยะทาง (รถบริษัท)" : "สร้างฐานราคาตามระยะทาง (รถบริษัท)" }) }),
2356
2447
  /* @__PURE__ */ jsx(Drawer.Body, { children: /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit, className: "flex flex-col gap-y-4", children: [
2357
- /* @__PURE__ */ jsxs("div", { children: [
2358
- /* @__PURE__ */ jsx(Label, { htmlFor: "name", children: "ชื่อวัสดุ *" }),
2359
- /* @__PURE__ */ jsx(
2360
- Input,
2361
- {
2362
- id: "name",
2363
- value: formData.name,
2364
- onChange: (e) => setFormData({ ...formData, name: e.target.value }),
2365
- placeholder: "เช่น พลาสติกกันกระแทก, กล่องกระดาษ",
2366
- required: true
2367
- }
2368
- )
2369
- ] }),
2370
- /* @__PURE__ */ jsxs("div", { children: [
2371
- /* @__PURE__ */ jsx(Label, { htmlFor: "category", children: "หมวดหมู่ (ไม่บังคับ)" }),
2372
- /* @__PURE__ */ jsxs(
2373
- Select,
2374
- {
2375
- value: formData.category || void 0,
2376
- onValueChange: (value) => setFormData({ ...formData, category: value }),
2377
- children: [
2378
- /* @__PURE__ */ jsx(Select.Trigger, { children: /* @__PURE__ */ jsx(Select.Value, { placeholder: "เลือกหมวดหมู่ (ไม่บังคับ)" }) }),
2379
- /* @__PURE__ */ jsx(Select.Content, { children: CATEGORIES$1.map((category) => /* @__PURE__ */ jsx(Select.Item, { value: category.value, children: category.label }, category.value)) })
2380
- ]
2381
- }
2382
- )
2383
- ] }),
2384
2448
  /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-x-4", children: [
2385
2449
  /* @__PURE__ */ jsxs("div", { children: [
2386
- /* @__PURE__ */ jsx(Label, { htmlFor: "unit", children: "หน่วย *" }),
2450
+ /* @__PURE__ */ jsx(Label, { htmlFor: "min_distance_km", children: "ระยะทางเริ่มต้น (กม.) *" }),
2387
2451
  /* @__PURE__ */ jsx(
2388
2452
  Input,
2389
2453
  {
2390
- id: "unit",
2391
- value: formData.unit,
2392
- onChange: (e) => setFormData({ ...formData, unit: e.target.value }),
2393
- placeholder: "เช่น ชิ้น, เมตร, กิโลกรัม",
2454
+ id: "min_distance_km",
2455
+ type: "number",
2456
+ min: "0",
2457
+ step: "0.1",
2458
+ value: formData.min_distance_km,
2459
+ onChange: (e) => setFormData({ ...formData, min_distance_km: e.target.value }),
2394
2460
  required: true
2395
2461
  }
2396
2462
  )
2397
2463
  ] }),
2398
2464
  /* @__PURE__ */ jsxs("div", { children: [
2399
- /* @__PURE__ */ jsx(Label, { htmlFor: "costPerUnit", children: "ต้นทุนต่อหน่วย *" }),
2465
+ /* @__PURE__ */ jsx(Label, { htmlFor: "max_distance_km", children: "ระยะทางสูงสุด (กม.)" }),
2400
2466
  /* @__PURE__ */ jsx(
2401
2467
  Input,
2402
2468
  {
2403
- id: "costPerUnit",
2469
+ id: "max_distance_km",
2404
2470
  type: "number",
2405
- step: "0.01",
2406
- value: formData.cost_per_unit,
2407
- onChange: (e) => setFormData({ ...formData, cost_per_unit: e.target.value }),
2408
- placeholder: "0.00",
2409
- required: true
2471
+ min: "0",
2472
+ step: "0.1",
2473
+ placeholder: "ปล่อยว่างหากไม่มีที่สิ้นสุด",
2474
+ value: formData.max_distance_km,
2475
+ onChange: (e) => setFormData({ ...formData, max_distance_km: e.target.value })
2410
2476
  }
2411
- )
2477
+ ),
2478
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-ui-fg-subtle mt-1", children: "เว้นว่างเพื่อกำหนดช่วงราคาแบบ 30+ กม." })
2412
2479
  ] })
2413
2480
  ] }),
2414
2481
  /* @__PURE__ */ jsxs("div", { children: [
2415
- /* @__PURE__ */ jsx(Label, { htmlFor: "currency", children: "สกุลเงิน" }),
2482
+ /* @__PURE__ */ jsx(Label, { htmlFor: "price", children: "ราคา (THB) *" }),
2416
2483
  /* @__PURE__ */ jsx(
2417
2484
  Input,
2418
2485
  {
2419
- id: "currency",
2420
- value: formData.currency,
2421
- onChange: (e) => setFormData({ ...formData, currency: e.target.value }),
2422
- placeholder: "THB"
2486
+ id: "price",
2487
+ type: "number",
2488
+ min: "0",
2489
+ step: "0.01",
2490
+ value: formData.price,
2491
+ onChange: (e) => setFormData({ ...formData, price: e.target.value }),
2492
+ required: true
2423
2493
  }
2424
2494
  )
2425
2495
  ] }),
2426
2496
  /* @__PURE__ */ jsxs("div", { children: [
2427
- /* @__PURE__ */ jsx(Label, { htmlFor: "description", children: "คำอธิบาย" }),
2497
+ /* @__PURE__ */ jsx(Label, { htmlFor: "priority", children: "ลำดับความสำคัญ" }),
2428
2498
  /* @__PURE__ */ jsx(
2429
- Textarea,
2499
+ Input,
2430
2500
  {
2431
- id: "description",
2432
- value: formData.description,
2433
- onChange: (e) => setFormData({ ...formData, description: e.target.value }),
2434
- placeholder: "คำอธิบายเพิ่มเติมเกี่ยวกับวัสดุนี้...",
2435
- rows: 3
2501
+ id: "priority",
2502
+ type: "number",
2503
+ value: formData.priority,
2504
+ onChange: (e) => setFormData({ ...formData, priority: e.target.value }),
2505
+ placeholder: "0"
2436
2506
  }
2437
- )
2507
+ ),
2508
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-ui-fg-subtle mt-1", children: "ใช้สำหรับจัดลำดับเมื่อมีช่วงราคาทับกัน (ค่าเริ่มต้น 0)" })
2438
2509
  ] }),
2439
2510
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-x-2", children: [
2440
2511
  /* @__PURE__ */ jsx(
@@ -2463,227 +2534,148 @@ const MaterialCostModal = ({
2463
2534
  ] }) })
2464
2535
  ] }) });
2465
2536
  };
2466
- const CATEGORIES = [
2467
- { value: "ALL", label: "ทั้งหมด" },
2468
- { value: "PACKAGING", label: "วัสดุหีบห่อ" },
2469
- { value: "PROTECTION", label: "วัสดุป้องกัน" },
2470
- { value: "FILLING", label: "วัสดุเติมช่องว่าง" },
2471
- { value: "TAPE", label: "เทป/กาว" },
2472
- { value: "LABEL", label: "ฉลาก/สติกเกอร์" },
2473
- { value: "OTHER", label: "อื่นๆ" }
2474
- ];
2475
- const MaterialCostsTable = () => {
2476
- const [materialCosts, setMaterialCosts] = useState([]);
2477
- const [filteredMaterialCosts, setFilteredMaterialCosts] = useState([]);
2478
- const [searchTerm, setSearchTerm] = useState("");
2479
- const [categoryFilter, setCategoryFilter] = useState("ALL");
2480
- const [isModalOpen, setIsModalOpen] = useState(false);
2481
- const [editingMaterialCost, setEditingMaterialCost] = useState(null);
2537
+ const CompanyTruckBasePricingTable = () => {
2538
+ const [rates, setRates] = useState([]);
2482
2539
  const [isLoading, setIsLoading] = useState(false);
2483
- const [deleteMaterialCostId, setDeleteMaterialCostId] = useState(null);
2484
- const [deleteMaterialCostName, setDeleteMaterialCostName] = useState("");
2540
+ const [isModalOpen, setIsModalOpen] = useState(false);
2541
+ const [editingRate, setEditingRate] = useState(null);
2542
+ const [deleteId, setDeleteId] = useState(null);
2485
2543
  useEffect(() => {
2486
- fetchMaterialCosts();
2544
+ fetchRates();
2487
2545
  }, []);
2488
- useEffect(() => {
2489
- let filtered = materialCosts;
2490
- if (categoryFilter !== "ALL") {
2491
- filtered = filtered.filter((mc) => mc.category === categoryFilter);
2492
- }
2493
- if (searchTerm) {
2494
- filtered = filtered.filter(
2495
- (mc) => mc.name.toLowerCase().includes(searchTerm.toLowerCase())
2496
- );
2497
- }
2498
- setFilteredMaterialCosts(filtered);
2499
- }, [searchTerm, categoryFilter, materialCosts]);
2500
- const fetchMaterialCosts = async () => {
2546
+ const orderedRates = useMemo(() => {
2547
+ return [...rates].sort((a, b) => {
2548
+ if (a.min_distance_km !== b.min_distance_km) {
2549
+ return a.min_distance_km - b.min_distance_km;
2550
+ }
2551
+ const aMax = a.max_distance_km ?? Number.POSITIVE_INFINITY;
2552
+ const bMax = b.max_distance_km ?? Number.POSITIVE_INFINITY;
2553
+ return aMax - bMax;
2554
+ });
2555
+ }, [rates]);
2556
+ const fetchRates = async () => {
2501
2557
  setIsLoading(true);
2502
- try {
2503
- const response = await fetch("/admin/material-costs", {
2504
- credentials: "include"
2505
- });
2558
+ try {
2559
+ const response = await fetch(
2560
+ "/admin/base-shipping-prices?carrier_type=COMPANY_TRUCK&service_code=SAME_DAY",
2561
+ {
2562
+ credentials: "include"
2563
+ }
2564
+ );
2565
+ if (!response.ok) {
2566
+ throw new Error("failed_to_load_base_rates");
2567
+ }
2506
2568
  const data = await response.json();
2507
- setMaterialCosts(data.material_costs || []);
2569
+ setRates(data.base_rates || []);
2508
2570
  } catch (error) {
2509
2571
  toast.error("ข้อผิดพลาด", {
2510
- description: "ไม่สามารถโหลดข้อมูลต้นทุนวัสดุได้"
2572
+ description: "ไม่สามารถโหลดฐานราคาตามระยะทางได้"
2511
2573
  });
2512
2574
  } finally {
2513
2575
  setIsLoading(false);
2514
2576
  }
2515
2577
  };
2516
- const handleToggleActive = async (materialCost) => {
2517
- try {
2518
- const response = await fetch(`/admin/material-costs/${materialCost.id}`, {
2519
- method: "PUT",
2520
- headers: { "Content-Type": "application/json" },
2521
- credentials: "include",
2522
- body: JSON.stringify({ ...materialCost, active: !materialCost.active })
2523
- });
2524
- if (response.ok) {
2525
- toast.success("สำเร็จ", {
2526
- description: `${materialCost.active ? "ปิดใช้งาน" : "เปิดใช้งาน"}วัสดุ ${materialCost.name} แล้ว`
2527
- });
2528
- fetchMaterialCosts();
2529
- } else {
2530
- throw new Error("Failed to update");
2531
- }
2532
- } catch (error) {
2533
- toast.error("ข้อผิดพลาด", {
2534
- description: "ไม่สามารถอัพเดทสถานะได้"
2535
- });
2536
- }
2537
- };
2538
2578
  const handleDelete = async () => {
2539
- if (!deleteMaterialCostId) return;
2579
+ if (!deleteId) return;
2540
2580
  try {
2541
2581
  const response = await fetch(
2542
- `/admin/material-costs/${deleteMaterialCostId}`,
2582
+ `/admin/base-shipping-prices/${deleteId}`,
2543
2583
  {
2544
2584
  method: "DELETE",
2545
2585
  credentials: "include"
2546
2586
  }
2547
2587
  );
2548
- if (response.ok) {
2549
- toast.success("สำเร็จ", {
2550
- description: `ลบวัสดุ ${deleteMaterialCostName} แล้ว`
2551
- });
2552
- fetchMaterialCosts();
2553
- } else {
2554
- throw new Error("Failed to delete");
2588
+ if (!response.ok) {
2589
+ throw new Error("failed_to_delete");
2555
2590
  }
2591
+ toast.success("สำเร็จ", {
2592
+ description: "ลบฐานราคาสำเร็จ"
2593
+ });
2594
+ fetchRates();
2556
2595
  } catch (error) {
2557
2596
  toast.error("ข้อผิดพลาด", {
2558
- description: "ไม่สามารถลบวัสดุได้"
2597
+ description: "ไม่สามารถลบฐานราคาได้"
2559
2598
  });
2560
2599
  } finally {
2561
- setDeleteMaterialCostId(null);
2562
- setDeleteMaterialCostName("");
2600
+ setDeleteId(null);
2563
2601
  }
2564
2602
  };
2565
- const handleEdit = (materialCost) => {
2566
- setEditingMaterialCost(materialCost);
2567
- setIsModalOpen(true);
2568
- };
2569
- const handleCreateNew = () => {
2570
- setEditingMaterialCost(null);
2571
- setIsModalOpen(true);
2572
- };
2573
2603
  const handleModalClose = () => {
2574
2604
  setIsModalOpen(false);
2575
- setEditingMaterialCost(null);
2576
- fetchMaterialCosts();
2577
- };
2578
- const openDeletePrompt = (id, name) => {
2579
- setDeleteMaterialCostId(id);
2580
- setDeleteMaterialCostName(name);
2605
+ setEditingRate(null);
2606
+ fetchRates();
2581
2607
  };
2582
- const getCategoryLabel = (category) => {
2583
- var _a;
2584
- if (!category) return "-";
2585
- return ((_a = CATEGORIES.find((c) => c.value === category)) == null ? void 0 : _a.label) || category;
2608
+ const formatDistanceRange = (rate) => {
2609
+ const min = rate.min_distance_km ?? 0;
2610
+ const max = rate.max_distance_km;
2611
+ if (max === void 0 || max === null) {
2612
+ return `${min}+ กม.`;
2613
+ }
2614
+ return `${min}-${max} กม.`;
2586
2615
  };
2587
2616
  return /* @__PURE__ */ jsxs(Fragment, { children: [
2588
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-y-4", children: [
2589
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-x-4", children: [
2590
- /* @__PURE__ */ jsxs("div", { className: "flex gap-x-4 flex-1", children: [
2617
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-x-4", children: [
2618
+ /* @__PURE__ */ jsxs("div", { children: [
2619
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-semibold", children: "ฐานราคาตามระยะทาง (รถบริษัท - Same Day)" }),
2620
+ /* @__PURE__ */ jsx("p", { className: "text-ui-fg-subtle text-sm", children: "กำหนดราคาเริ่มต้นตามระยะทาง เช่น 0-10, 10-30, 30-50, 50+ กม." })
2621
+ ] }),
2622
+ /* @__PURE__ */ jsxs(Button, { onClick: () => setIsModalOpen(true), children: [
2623
+ /* @__PURE__ */ jsx(Plus, {}),
2624
+ "เพิ่มช่วงราคา"
2625
+ ] })
2626
+ ] }),
2627
+ /* @__PURE__ */ jsxs(Table, { className: "mt-4", children: [
2628
+ /* @__PURE__ */ jsx(Table.Header, { children: /* @__PURE__ */ jsxs(Table.Row, { children: [
2629
+ /* @__PURE__ */ jsx(Table.HeaderCell, { children: "ช่วงระยะทาง" }),
2630
+ /* @__PURE__ */ jsx(Table.HeaderCell, { children: "ราคา (THB)" }),
2631
+ /* @__PURE__ */ jsx(Table.HeaderCell, { children: "ลำดับ" }),
2632
+ /* @__PURE__ */ jsx(Table.HeaderCell, { children: "สถานะ" }),
2633
+ /* @__PURE__ */ jsx(Table.HeaderCell, { className: "text-right", children: "การจัดการ" })
2634
+ ] }) }),
2635
+ /* @__PURE__ */ jsx(Table.Body, { children: isLoading ? /* @__PURE__ */ jsx(Table.Row, { children: /* @__PURE__ */ jsx(Table.Cell, { colSpan: 5, className: "text-center", children: "กำลังโหลด..." }) }) : orderedRates.length === 0 ? /* @__PURE__ */ jsx(Table.Row, { children: /* @__PURE__ */ jsx(Table.Cell, { colSpan: 5, className: "text-center", children: "ยังไม่ได้ตั้งค่าฐานราคาตามระยะทาง (รถบริษัทจะใช้ราคาตามน้ำหนัก)" }) }) : orderedRates.map((rate) => /* @__PURE__ */ jsxs(Table.Row, { children: [
2636
+ /* @__PURE__ */ jsx(Table.Cell, { children: formatDistanceRange(rate) }),
2637
+ /* @__PURE__ */ jsxs(Table.Cell, { children: [
2638
+ "฿",
2639
+ rate.price.toFixed(2)
2640
+ ] }),
2641
+ /* @__PURE__ */ jsx(Table.Cell, { children: rate.priority ?? 0 }),
2642
+ /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(Badge, { color: rate.active ? "green" : "grey", children: rate.active ? "เปิดใช้งาน" : "ปิดใช้งาน" }) }),
2643
+ /* @__PURE__ */ jsx(Table.Cell, { className: "text-right", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-x-2", children: [
2591
2644
  /* @__PURE__ */ jsx(
2592
- Input,
2645
+ Button,
2593
2646
  {
2594
- type: "search",
2595
- placeholder: "ค้นหาชื่อวัสดุ...",
2596
- value: searchTerm,
2597
- onChange: (e) => setSearchTerm(e.target.value),
2598
- className: "max-w-md"
2647
+ variant: "transparent",
2648
+ size: "small",
2649
+ onClick: () => {
2650
+ setEditingRate(rate);
2651
+ setIsModalOpen(true);
2652
+ },
2653
+ children: /* @__PURE__ */ jsx(Edit, {})
2599
2654
  }
2600
2655
  ),
2601
- /* @__PURE__ */ jsxs(Select, { value: categoryFilter, onValueChange: setCategoryFilter, children: [
2602
- /* @__PURE__ */ jsx(Select.Trigger, { children: /* @__PURE__ */ jsx(Select.Value, { placeholder: "หมวดหมู่" }) }),
2603
- /* @__PURE__ */ jsx(Select.Content, { children: CATEGORIES.map((category) => /* @__PURE__ */ jsx(Select.Item, { value: category.value, children: category.label }, category.value)) })
2604
- ] })
2605
- ] }),
2606
- /* @__PURE__ */ jsxs(Button, { onClick: handleCreateNew, children: [
2607
- /* @__PURE__ */ jsx(Plus, {}),
2608
- "สร้างรายการใหม่"
2609
- ] })
2610
- ] }),
2611
- /* @__PURE__ */ jsxs(Table, { children: [
2612
- /* @__PURE__ */ jsx(Table.Header, { children: /* @__PURE__ */ jsxs(Table.Row, { children: [
2613
- /* @__PURE__ */ jsx(Table.HeaderCell, { children: "ชื่อ" }),
2614
- /* @__PURE__ */ jsx(Table.HeaderCell, { children: "หมวดหมู่" }),
2615
- /* @__PURE__ */ jsx(Table.HeaderCell, { children: "หน่วย" }),
2616
- /* @__PURE__ */ jsx(Table.HeaderCell, { children: "ต้นทุนต่อหน่วย" }),
2617
- /* @__PURE__ */ jsx(Table.HeaderCell, { children: "คำอธิบาย" }),
2618
- /* @__PURE__ */ jsx(Table.HeaderCell, { children: "สถานะ" }),
2619
- /* @__PURE__ */ jsx(Table.HeaderCell, { className: "text-right", children: "การจัดการ" })
2620
- ] }) }),
2621
- /* @__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: [
2622
- /* @__PURE__ */ jsx(Table.Cell, { children: materialCost.name }),
2623
- /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(Badge, { color: "blue", children: getCategoryLabel(materialCost.category) }) }),
2624
- /* @__PURE__ */ jsx(Table.Cell, { children: materialCost.unit }),
2625
- /* @__PURE__ */ jsxs(Table.Cell, { children: [
2626
- materialCost.cost_per_unit.toFixed(2),
2627
- " ",
2628
- materialCost.currency
2629
- ] }),
2630
- /* @__PURE__ */ jsx(Table.Cell, { className: "max-w-xs truncate", children: materialCost.description || "-" }),
2631
- /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(
2632
- Badge,
2656
+ /* @__PURE__ */ jsx(
2657
+ Button,
2633
2658
  {
2634
- color: materialCost.active ? "green" : "grey",
2635
- className: "cursor-pointer",
2636
- onClick: () => handleToggleActive(materialCost),
2637
- children: materialCost.active ? "เปิดใช้งาน" : "ปิดใช้งาน"
2659
+ variant: "transparent",
2660
+ size: "small",
2661
+ onClick: () => setDeleteId(rate.id),
2662
+ children: /* @__PURE__ */ jsx(Trash, {})
2638
2663
  }
2639
- ) }),
2640
- /* @__PURE__ */ jsx(Table.Cell, { className: "text-right", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-x-2", children: [
2641
- /* @__PURE__ */ jsx(
2642
- Button,
2643
- {
2644
- variant: "transparent",
2645
- size: "small",
2646
- onClick: () => handleEdit(materialCost),
2647
- children: /* @__PURE__ */ jsx(Edit, {})
2648
- }
2649
- ),
2650
- /* @__PURE__ */ jsx(
2651
- Button,
2652
- {
2653
- variant: "transparent",
2654
- size: "small",
2655
- onClick: () => openDeletePrompt(materialCost.id, materialCost.name),
2656
- children: /* @__PURE__ */ jsx(Trash, {})
2657
- }
2658
- )
2659
- ] }) })
2660
- ] }, materialCost.id)) })
2661
- ] })
2664
+ )
2665
+ ] }) })
2666
+ ] }, rate.id)) })
2662
2667
  ] }),
2663
- isModalOpen && /* @__PURE__ */ jsx(
2664
- MaterialCostModal,
2665
- {
2666
- materialCost: editingMaterialCost,
2667
- onClose: handleModalClose
2668
- }
2669
- ),
2668
+ isModalOpen && /* @__PURE__ */ jsx(CompanyTruckBaseRateModal, { rate: editingRate, onClose: handleModalClose }),
2670
2669
  /* @__PURE__ */ jsx(
2671
2670
  Prompt,
2672
2671
  {
2673
2672
  variant: "confirmation",
2674
- open: !!deleteMaterialCostId,
2675
- onOpenChange: () => {
2676
- setDeleteMaterialCostId(null);
2677
- setDeleteMaterialCostName("");
2678
- },
2673
+ open: !!deleteId,
2674
+ onOpenChange: () => setDeleteId(null),
2679
2675
  children: /* @__PURE__ */ jsxs(Prompt.Content, { children: [
2680
2676
  /* @__PURE__ */ jsxs(Prompt.Header, { children: [
2681
- /* @__PURE__ */ jsx(Prompt.Title, { children: "ลบรายการต้นทุนวัสดุ" }),
2682
- /* @__PURE__ */ jsxs(Prompt.Description, { children: [
2683
- 'คุณต้องการลบวัสดุ "',
2684
- deleteMaterialCostName,
2685
- '" ใช่หรือไม่? การดำเนินการนี้ไม่สามารถย้อนกลับได้'
2686
- ] })
2677
+ /* @__PURE__ */ jsx(Prompt.Title, { children: "ลบฐานราคา" }),
2678
+ /* @__PURE__ */ jsx(Prompt.Description, { children: "คุณต้องการลบช่วงราคานี้ใช่หรือไม่? การดำเนินการนี้ไม่สามารถย้อนกลับได้" })
2687
2679
  ] }),
2688
2680
  /* @__PURE__ */ jsxs(Prompt.Footer, { children: [
2689
2681
  /* @__PURE__ */ jsx(Prompt.Cancel, { children: "ยกเลิก" }),
@@ -2694,15 +2686,23 @@ const MaterialCostsTable = () => {
2694
2686
  )
2695
2687
  ] });
2696
2688
  };
2697
- const MaterialCostsPage = () => {
2698
- return /* @__PURE__ */ jsx(Container, { children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-y-4", children: [
2699
- /* @__PURE__ */ jsx("div", { className: "flex items-center justify-between", children: /* @__PURE__ */ jsx(Heading, { level: "h1", children: "ต้นทุนวัสดุ" }) }),
2700
- /* @__PURE__ */ jsx(MaterialCostsTable, {})
2689
+ const RatesPage = () => {
2690
+ return /* @__PURE__ */ jsx(Container, { children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-y-8", children: [
2691
+ /* @__PURE__ */ jsx("div", { className: "flex items-center justify-between", children: /* @__PURE__ */ jsx(Heading, { level: "h1", children: "อัตราค่าขนส่ง" }) }),
2692
+ /* @__PURE__ */ jsx(ShippingRatesTable, {}),
2693
+ /* @__PURE__ */ jsxs("div", { className: "border-t pt-4", children: [
2694
+ /* @__PURE__ */ jsx(Heading, { level: "h2", className: "mb-2 text-lg", children: "ฐานราคาตามระยะทาง (Messenger 3H)" }),
2695
+ /* @__PURE__ */ jsx(MessengerBasePricingTable, {})
2696
+ ] }),
2697
+ /* @__PURE__ */ jsxs("div", { className: "border-t pt-4", children: [
2698
+ /* @__PURE__ */ jsx(Heading, { level: "h2", className: "mb-2 text-lg", children: "ฐานราคาตามระยะทาง (รถบริษัท - Same Day)" }),
2699
+ /* @__PURE__ */ jsx(CompanyTruckBasePricingTable, {})
2700
+ ] })
2701
2701
  ] }) });
2702
2702
  };
2703
2703
  const config = defineRouteConfig({
2704
- icon: CurrencyDollarSolid,
2705
- label: "ต้นทุนวัสดุ"
2704
+ icon: HandTruck,
2705
+ label: "อัตราค่าขนส่ง"
2706
2706
  });
2707
2707
  const widgetModule = { widgets: [
2708
2708
  {
@@ -2724,13 +2724,13 @@ const routeModule = {
2724
2724
  Component: BoxesPage,
2725
2725
  path: "/boxes"
2726
2726
  },
2727
- {
2728
- Component: RatesPage,
2729
- path: "/rates"
2730
- },
2731
2727
  {
2732
2728
  Component: MaterialCostsPage,
2733
2729
  path: "/material-costs"
2730
+ },
2731
+ {
2732
+ Component: RatesPage,
2733
+ path: "/rates"
2734
2734
  }
2735
2735
  ]
2736
2736
  };
@@ -2761,16 +2761,16 @@ const menuItemModule = {
2761
2761
  translationNs: void 0
2762
2762
  },
2763
2763
  {
2764
- label: config.label,
2765
- icon: config.icon,
2764
+ label: config$1.label,
2765
+ icon: config$1.icon,
2766
2766
  path: "/material-costs",
2767
2767
  nested: void 0,
2768
2768
  rank: void 0,
2769
2769
  translationNs: void 0
2770
2770
  },
2771
2771
  {
2772
- label: config$1.label,
2773
- icon: config$1.icon,
2772
+ label: config.label,
2773
+ icon: config.icon,
2774
2774
  path: "/rates",
2775
2775
  nested: void 0,
2776
2776
  rank: void 0,