@lodashventure/medusa-parcel-shipping 0.4.29 → 0.4.31

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