Fix Layout
This commit is contained in:
@@ -141,7 +141,6 @@ export default function DashboardPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{/* Header / Filters */}
|
|
||||||
<div className="flex flex-col gap-3 sm:flex-row sm:items-end sm:justify-between">
|
<div className="flex flex-col gap-3 sm:flex-row sm:items-end sm:justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-3xl font-extrabold tracking-tight">EOP Dashboard</h1>
|
<h1 className="text-3xl font-extrabold tracking-tight">EOP Dashboard</h1>
|
||||||
|
|||||||
@@ -1,22 +1,8 @@
|
|||||||
// File: src/app/(protected)/orders/page.tsx
|
// File: src/app/(protected)/orders/page.tsx
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
/**
|
|
||||||
* EOP Orders — มุมมองเต็ม (Full) + ตัวกรองแบบ Select + Modal ปรับแต่งคอลัมน์
|
|
||||||
* - เฮดเดอร์ตารางแบบ 2 ชั้น (กลุ่มหัวข้อ ➜ หัวคอลัมน์ย่อย) ภาษาไทยทั้งหมด
|
|
||||||
* - เลือกคอลัมน์ที่ต้องการแสดงได้จาก "ปรับแต่งตาราง" (จำค่าบน localStorage)
|
|
||||||
* - แก้ Modal ให้เนื้อหาเลื่อน (scroll) ได้ และปุ่ม Apply ชิดด้านล่างเสมอ
|
|
||||||
* - เพิ่มฟิลด์ "ผู้เปิดออเดอร์" (createdBy*)
|
|
||||||
* - ตารางไม่ fix size โดยให้คอนเทนต์กำหนดความกว้างเอง และให้ทุกเซลล์แสดงบรรทัดเดียว (nowrap)
|
|
||||||
*
|
|
||||||
* หมายเหตุ: ใช้ mock แค่ไม่กี่เรคคอร์ดเพื่อโชว์ UX — ต่อ API จริงได้โดยแทนที่ MOCK_ORDERS
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { useEffect, useMemo, useState, type ReactNode } from "react";
|
import { useEffect, useMemo, useState, type ReactNode } from "react";
|
||||||
|
|
||||||
/* ===========================
|
|
||||||
Types
|
|
||||||
=========================== */
|
|
||||||
type Channel = "shopee" | "lazada" | "tiktok" | "d2c" | "chat";
|
type Channel = "shopee" | "lazada" | "tiktok" | "d2c" | "chat";
|
||||||
type PaymentStatus = "paid" | "pending" | "failed" | "cod" | "refunded";
|
type PaymentStatus = "paid" | "pending" | "failed" | "cod" | "refunded";
|
||||||
type FulfillmentStatus =
|
type FulfillmentStatus =
|
||||||
@@ -28,7 +14,6 @@ type FulfillmentStatus =
|
|||||||
| "cancelled";
|
| "cancelled";
|
||||||
|
|
||||||
type Order = {
|
type Order = {
|
||||||
// เอกลักษณ์ & ช่องทาง
|
|
||||||
id: string;
|
id: string;
|
||||||
orderNo: string;
|
orderNo: string;
|
||||||
orderRef: string;
|
orderRef: string;
|
||||||
@@ -36,27 +21,19 @@ type Order = {
|
|||||||
channelOrderId: string;
|
channelOrderId: string;
|
||||||
marketplaceShopId: string;
|
marketplaceShopId: string;
|
||||||
marketplaceShopName: string;
|
marketplaceShopName: string;
|
||||||
|
|
||||||
// ผู้เปิดออเดอร์
|
|
||||||
createdById: string;
|
createdById: string;
|
||||||
createdByName: string;
|
createdByName: string;
|
||||||
createdByEmail?: string | null;
|
createdByEmail?: string | null;
|
||||||
|
|
||||||
// วัน–เวลา (ISO)
|
|
||||||
dateCreated: string;
|
dateCreated: string;
|
||||||
datePaid?: string | null;
|
datePaid?: string | null;
|
||||||
datePacked?: string | null;
|
datePacked?: string | null;
|
||||||
dateShipped?: string | null;
|
dateShipped?: string | null;
|
||||||
dateDelivered?: string | null;
|
dateDelivered?: string | null;
|
||||||
dateCancelled?: string | null;
|
dateCancelled?: string | null;
|
||||||
|
|
||||||
// ลูกค้า
|
|
||||||
customerId: string;
|
customerId: string;
|
||||||
customerName: string;
|
customerName: string;
|
||||||
customerEmail?: string | null;
|
customerEmail?: string | null;
|
||||||
customerPhone?: string | null;
|
customerPhone?: string | null;
|
||||||
|
|
||||||
// ที่อยู่จัดส่ง
|
|
||||||
shippingName: string;
|
shippingName: string;
|
||||||
shippingPhone?: string | null;
|
shippingPhone?: string | null;
|
||||||
shippingAddressLine1: string;
|
shippingAddressLine1: string;
|
||||||
@@ -66,8 +43,6 @@ type Order = {
|
|||||||
shippingProvince: string;
|
shippingProvince: string;
|
||||||
shippingPostcode: string;
|
shippingPostcode: string;
|
||||||
shippingCountry: string;
|
shippingCountry: string;
|
||||||
|
|
||||||
// ออกใบเสร็จ/ภาษี
|
|
||||||
billingName: string;
|
billingName: string;
|
||||||
billingTaxId?: string | null;
|
billingTaxId?: string | null;
|
||||||
billingAddressLine1: string;
|
billingAddressLine1: string;
|
||||||
@@ -76,17 +51,13 @@ type Order = {
|
|||||||
billingProvince: string;
|
billingProvince: string;
|
||||||
billingPostcode: string;
|
billingPostcode: string;
|
||||||
billingCountry: string;
|
billingCountry: string;
|
||||||
|
|
||||||
// การชำระเงิน
|
|
||||||
paymentMethod: "card" | "bank_qr" | "cod" | "wallet" | "transfer";
|
paymentMethod: "card" | "bank_qr" | "cod" | "wallet" | "transfer";
|
||||||
paymentStatus: PaymentStatus;
|
paymentStatus: PaymentStatus;
|
||||||
paymentProvider?: string | null;
|
paymentProvider?: string | null;
|
||||||
paymentTxId?: string | null;
|
paymentTxId?: string | null;
|
||||||
paymentFeeTHB?: number | null;
|
paymentFeeTHB?: number | null;
|
||||||
|
|
||||||
// ราคา/ต้นทุนย่อย
|
|
||||||
currency: "THB";
|
currency: "THB";
|
||||||
exchangeRate: number; // 1 สำหรับ THB
|
exchangeRate: number;
|
||||||
subtotalTHB: number;
|
subtotalTHB: number;
|
||||||
discountTHB: number;
|
discountTHB: number;
|
||||||
shippingFeeTHB: number;
|
shippingFeeTHB: number;
|
||||||
@@ -94,30 +65,22 @@ type Order = {
|
|||||||
packagingFeeTHB: number;
|
packagingFeeTHB: number;
|
||||||
taxTHB: number;
|
taxTHB: number;
|
||||||
totalTHB: number;
|
totalTHB: number;
|
||||||
|
|
||||||
// สินค้า/โลจิสติกส์
|
|
||||||
itemsCount: number;
|
itemsCount: number;
|
||||||
totalWeightGrams: number;
|
totalWeightGrams: number;
|
||||||
packageCount: number;
|
packageCount: number;
|
||||||
dimensionsCmL?: number | null;
|
dimensionsCmL?: number | null;
|
||||||
dimensionsCmW?: number | null;
|
dimensionsCmW?: number | null;
|
||||||
dimensionsCmH?: number | null;
|
dimensionsCmH?: number | null;
|
||||||
|
|
||||||
// สถานะจัดส่ง
|
|
||||||
fulfillStatus: FulfillmentStatus;
|
fulfillStatus: FulfillmentStatus;
|
||||||
shipBy?: string | null;
|
shipBy?: string | null;
|
||||||
carrier?: string | null;
|
carrier?: string | null;
|
||||||
serviceLevel?: string | null;
|
serviceLevel?: string | null;
|
||||||
trackingNo?: string | null;
|
trackingNo?: string | null;
|
||||||
|
|
||||||
// โกดัง/ปฏิบัติการ
|
|
||||||
warehouseCode: "BKK-DC" | "CNX-HUB" | "HKT-MINI";
|
warehouseCode: "BKK-DC" | "CNX-HUB" | "HKT-MINI";
|
||||||
pickListId?: string | null;
|
pickListId?: string | null;
|
||||||
waveId?: string | null;
|
waveId?: string | null;
|
||||||
binFrom?: string | null;
|
binFrom?: string | null;
|
||||||
binTo?: string | null;
|
binTo?: string | null;
|
||||||
|
|
||||||
// ธง/โน้ต/ความเสี่ยง
|
|
||||||
tags?: string[];
|
tags?: string[];
|
||||||
rmaFlag?: boolean;
|
rmaFlag?: boolean;
|
||||||
rmaReason?: string | null;
|
rmaReason?: string | null;
|
||||||
@@ -135,15 +98,12 @@ type Order = {
|
|||||||
type ColumnKey = keyof Order;
|
type ColumnKey = keyof Order;
|
||||||
type Column = {
|
type Column = {
|
||||||
key: ColumnKey;
|
key: ColumnKey;
|
||||||
labelTH: string; // ป้ายหัวคอลัมน์ (ไทย)
|
labelTH: string;
|
||||||
groupTH: string; // กลุ่มหัวข้อชั้นบน (ไทย)
|
groupTH: string;
|
||||||
align?: "left" | "right";
|
align?: "left" | "right";
|
||||||
format?: (v: Order[ColumnKey], row: Order) => ReactNode;
|
format?: (v: Order[ColumnKey], row: Order) => ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* ===========================
|
|
||||||
Mock (ไม่กี่เรคคอร์ด)
|
|
||||||
=========================== */
|
|
||||||
const MOCK_ORDERS: Order[] = [
|
const MOCK_ORDERS: Order[] = [
|
||||||
{
|
{
|
||||||
id: "1",
|
id: "1",
|
||||||
@@ -153,23 +113,19 @@ const MOCK_ORDERS: Order[] = [
|
|||||||
channelOrderId: "SP-9988776655",
|
channelOrderId: "SP-9988776655",
|
||||||
marketplaceShopId: "sp_shop_10293",
|
marketplaceShopId: "sp_shop_10293",
|
||||||
marketplaceShopName: "Amrez Beauty Official",
|
marketplaceShopName: "Amrez Beauty Official",
|
||||||
|
|
||||||
createdById: "U-001",
|
createdById: "U-001",
|
||||||
createdByName: "อ้อม ส.",
|
createdByName: "อ้อม ส.",
|
||||||
createdByEmail: "aom@amrez.co.th",
|
createdByEmail: "aom@amrez.co.th",
|
||||||
|
|
||||||
dateCreated: "2025-10-08T08:15:00+07:00",
|
dateCreated: "2025-10-08T08:15:00+07:00",
|
||||||
datePaid: "2025-10-08T08:16:10+07:00",
|
datePaid: "2025-10-08T08:16:10+07:00",
|
||||||
datePacked: null,
|
datePacked: null,
|
||||||
dateShipped: null,
|
dateShipped: null,
|
||||||
dateDelivered: null,
|
dateDelivered: null,
|
||||||
dateCancelled: null,
|
dateCancelled: null,
|
||||||
|
|
||||||
customerId: "CUST-000142",
|
customerId: "CUST-000142",
|
||||||
customerName: "Ploy Ch.",
|
customerName: "Ploy Ch.",
|
||||||
customerEmail: "ploy@example.com",
|
customerEmail: "ploy@example.com",
|
||||||
customerPhone: "0812345678",
|
customerPhone: "0812345678",
|
||||||
|
|
||||||
shippingName: "Ploy Ch.",
|
shippingName: "Ploy Ch.",
|
||||||
shippingPhone: "0812345678",
|
shippingPhone: "0812345678",
|
||||||
shippingAddressLine1: "91/518 หมู่ 1",
|
shippingAddressLine1: "91/518 หมู่ 1",
|
||||||
@@ -179,7 +135,6 @@ const MOCK_ORDERS: Order[] = [
|
|||||||
shippingProvince: "นนทบุรี",
|
shippingProvince: "นนทบุรี",
|
||||||
shippingPostcode: "11000",
|
shippingPostcode: "11000",
|
||||||
shippingCountry: "TH",
|
shippingCountry: "TH",
|
||||||
|
|
||||||
billingName: "Ploy Ch.",
|
billingName: "Ploy Ch.",
|
||||||
billingTaxId: null,
|
billingTaxId: null,
|
||||||
billingAddressLine1: "91/518 หมู่ 1",
|
billingAddressLine1: "91/518 หมู่ 1",
|
||||||
@@ -188,13 +143,11 @@ const MOCK_ORDERS: Order[] = [
|
|||||||
billingProvince: "นนทบุรี",
|
billingProvince: "นนทบุรี",
|
||||||
billingPostcode: "11000",
|
billingPostcode: "11000",
|
||||||
billingCountry: "TH",
|
billingCountry: "TH",
|
||||||
|
|
||||||
paymentMethod: "bank_qr",
|
paymentMethod: "bank_qr",
|
||||||
paymentStatus: "paid",
|
paymentStatus: "paid",
|
||||||
paymentProvider: "2C2P",
|
paymentProvider: "2C2P",
|
||||||
paymentTxId: "2C2P_2a8e7b0b",
|
paymentTxId: "2C2P_2a8e7b0b",
|
||||||
paymentFeeTHB: 9.5,
|
paymentFeeTHB: 9.5,
|
||||||
|
|
||||||
currency: "THB",
|
currency: "THB",
|
||||||
exchangeRate: 1,
|
exchangeRate: 1,
|
||||||
subtotalTHB: 1550,
|
subtotalTHB: 1550,
|
||||||
@@ -204,26 +157,22 @@ const MOCK_ORDERS: Order[] = [
|
|||||||
packagingFeeTHB: 0,
|
packagingFeeTHB: 0,
|
||||||
taxTHB: 0,
|
taxTHB: 0,
|
||||||
totalTHB: 1490,
|
totalTHB: 1490,
|
||||||
|
|
||||||
itemsCount: 2,
|
itemsCount: 2,
|
||||||
totalWeightGrams: 420,
|
totalWeightGrams: 420,
|
||||||
packageCount: 1,
|
packageCount: 1,
|
||||||
dimensionsCmL: 22,
|
dimensionsCmL: 22,
|
||||||
dimensionsCmW: 16,
|
dimensionsCmW: 16,
|
||||||
dimensionsCmH: 10,
|
dimensionsCmH: 10,
|
||||||
|
|
||||||
fulfillStatus: "processing",
|
fulfillStatus: "processing",
|
||||||
shipBy: "2025-10-09T18:00:00+07:00",
|
shipBy: "2025-10-09T18:00:00+07:00",
|
||||||
carrier: null,
|
carrier: null,
|
||||||
serviceLevel: "Standard",
|
serviceLevel: "Standard",
|
||||||
trackingNo: null,
|
trackingNo: null,
|
||||||
|
|
||||||
warehouseCode: "BKK-DC",
|
warehouseCode: "BKK-DC",
|
||||||
pickListId: null,
|
pickListId: null,
|
||||||
waveId: null,
|
waveId: null,
|
||||||
binFrom: "A-01-03",
|
binFrom: "A-01-03",
|
||||||
binTo: null,
|
binTo: null,
|
||||||
|
|
||||||
tags: ["priority", "giftwrap"],
|
tags: ["priority", "giftwrap"],
|
||||||
rmaFlag: false,
|
rmaFlag: false,
|
||||||
rmaReason: null,
|
rmaReason: null,
|
||||||
@@ -245,23 +194,19 @@ const MOCK_ORDERS: Order[] = [
|
|||||||
channelOrderId: "LZ-5566778899",
|
channelOrderId: "LZ-5566778899",
|
||||||
marketplaceShopId: "lz_shop_8811",
|
marketplaceShopId: "lz_shop_8811",
|
||||||
marketplaceShopName: "Kathy Labz Store",
|
marketplaceShopName: "Kathy Labz Store",
|
||||||
|
|
||||||
createdById: "U-002",
|
createdById: "U-002",
|
||||||
createdByName: "บี ที.",
|
createdByName: "บี ที.",
|
||||||
createdByEmail: "bee@amrez.co.th",
|
createdByEmail: "bee@amrez.co.th",
|
||||||
|
|
||||||
dateCreated: "2025-10-08T08:22:00+07:00",
|
dateCreated: "2025-10-08T08:22:00+07:00",
|
||||||
datePaid: null,
|
datePaid: null,
|
||||||
datePacked: null,
|
datePacked: null,
|
||||||
dateShipped: null,
|
dateShipped: null,
|
||||||
dateDelivered: null,
|
dateDelivered: null,
|
||||||
dateCancelled: null,
|
dateCancelled: null,
|
||||||
|
|
||||||
customerId: "CUST-000320",
|
customerId: "CUST-000320",
|
||||||
customerName: "Thanawat K.",
|
customerName: "Thanawat K.",
|
||||||
customerEmail: "thanawat@example.com",
|
customerEmail: "thanawat@example.com",
|
||||||
customerPhone: "0820001111",
|
customerPhone: "0820001111",
|
||||||
|
|
||||||
shippingName: "Thanawat K.",
|
shippingName: "Thanawat K.",
|
||||||
shippingPhone: "0820001111",
|
shippingPhone: "0820001111",
|
||||||
shippingAddressLine1: "128/7 ซอยสวนหลวง",
|
shippingAddressLine1: "128/7 ซอยสวนหลวง",
|
||||||
@@ -271,7 +216,6 @@ const MOCK_ORDERS: Order[] = [
|
|||||||
shippingProvince: "กรุงเทพมหานคร",
|
shippingProvince: "กรุงเทพมหานคร",
|
||||||
shippingPostcode: "10200",
|
shippingPostcode: "10200",
|
||||||
shippingCountry: "TH",
|
shippingCountry: "TH",
|
||||||
|
|
||||||
billingName: "Thanawat K.",
|
billingName: "Thanawat K.",
|
||||||
billingTaxId: "0105561234567",
|
billingTaxId: "0105561234567",
|
||||||
billingAddressLine1: "128/7 ซอยสวนหลวง",
|
billingAddressLine1: "128/7 ซอยสวนหลวง",
|
||||||
@@ -280,13 +224,11 @@ const MOCK_ORDERS: Order[] = [
|
|||||||
billingProvince: "กรุงเทพมหานคร",
|
billingProvince: "กรุงเทพมหานคร",
|
||||||
billingPostcode: "10200",
|
billingPostcode: "10200",
|
||||||
billingCountry: "TH",
|
billingCountry: "TH",
|
||||||
|
|
||||||
paymentMethod: "cod",
|
paymentMethod: "cod",
|
||||||
paymentStatus: "cod",
|
paymentStatus: "cod",
|
||||||
paymentProvider: null,
|
paymentProvider: null,
|
||||||
paymentTxId: null,
|
paymentTxId: null,
|
||||||
paymentFeeTHB: 0,
|
paymentFeeTHB: 0,
|
||||||
|
|
||||||
currency: "THB",
|
currency: "THB",
|
||||||
exchangeRate: 1,
|
exchangeRate: 1,
|
||||||
subtotalTHB: 590,
|
subtotalTHB: 590,
|
||||||
@@ -296,26 +238,22 @@ const MOCK_ORDERS: Order[] = [
|
|||||||
packagingFeeTHB: 0,
|
packagingFeeTHB: 0,
|
||||||
taxTHB: 0,
|
taxTHB: 0,
|
||||||
totalTHB: 640,
|
totalTHB: 640,
|
||||||
|
|
||||||
itemsCount: 1,
|
itemsCount: 1,
|
||||||
totalWeightGrams: 220,
|
totalWeightGrams: 220,
|
||||||
packageCount: 1,
|
packageCount: 1,
|
||||||
dimensionsCmL: 18,
|
dimensionsCmL: 18,
|
||||||
dimensionsCmW: 12,
|
dimensionsCmW: 12,
|
||||||
dimensionsCmH: 8,
|
dimensionsCmH: 8,
|
||||||
|
|
||||||
fulfillStatus: "unfulfilled",
|
fulfillStatus: "unfulfilled",
|
||||||
shipBy: "2025-10-09T18:00:00+07:00",
|
shipBy: "2025-10-09T18:00+07:00",
|
||||||
carrier: "Flash",
|
carrier: "Flash",
|
||||||
serviceLevel: "COD-Standard",
|
serviceLevel: "COD-Standard",
|
||||||
trackingNo: null,
|
trackingNo: null,
|
||||||
|
|
||||||
warehouseCode: "BKK-DC",
|
warehouseCode: "BKK-DC",
|
||||||
pickListId: null,
|
pickListId: null,
|
||||||
waveId: "WAVE-101",
|
waveId: "WAVE-101",
|
||||||
binFrom: "C-02-11",
|
binFrom: "C-02-11",
|
||||||
binTo: null,
|
binTo: null,
|
||||||
|
|
||||||
tags: ["fragile"],
|
tags: ["fragile"],
|
||||||
rmaFlag: false,
|
rmaFlag: false,
|
||||||
rmaReason: null,
|
rmaReason: null,
|
||||||
@@ -337,23 +275,19 @@ const MOCK_ORDERS: Order[] = [
|
|||||||
channelOrderId: "TT-4433221100",
|
channelOrderId: "TT-4433221100",
|
||||||
marketplaceShopId: "tt_shop_5501",
|
marketplaceShopId: "tt_shop_5501",
|
||||||
marketplaceShopName: "Amrez TikTok",
|
marketplaceShopName: "Amrez TikTok",
|
||||||
|
|
||||||
createdById: "U-003",
|
createdById: "U-003",
|
||||||
createdByName: "กร พ.",
|
createdByName: "กร พ.",
|
||||||
createdByEmail: "korn@amrez.co.th",
|
createdByEmail: "korn@amrez.co.th",
|
||||||
|
|
||||||
dateCreated: "2025-10-08T08:40:00+07:00",
|
dateCreated: "2025-10-08T08:40:00+07:00",
|
||||||
datePaid: "2025-10-08T08:41:05+07:00",
|
datePaid: "2025-10-08T08:41:05+07:00",
|
||||||
datePacked: "2025-10-08T09:05:30+07:00",
|
datePacked: "2025-10-08T09:05:30+07:00",
|
||||||
dateShipped: "2025-10-08T12:10:00+07:00",
|
dateShipped: "2025-10-08T12:10:00+07:00",
|
||||||
dateDelivered: null,
|
dateDelivered: null,
|
||||||
dateCancelled: null,
|
dateCancelled: null,
|
||||||
|
|
||||||
customerId: "CUST-000511",
|
customerId: "CUST-000511",
|
||||||
customerName: "Mint W.",
|
customerName: "Mint W.",
|
||||||
customerEmail: "mint@example.com",
|
customerEmail: "mint@example.com",
|
||||||
customerPhone: "0869997777",
|
customerPhone: "0869997777",
|
||||||
|
|
||||||
shippingName: "Mint W.",
|
shippingName: "Mint W.",
|
||||||
shippingPhone: "0869997777",
|
shippingPhone: "0869997777",
|
||||||
shippingAddressLine1: "55/9 ถนนสายน้ำ",
|
shippingAddressLine1: "55/9 ถนนสายน้ำ",
|
||||||
@@ -363,7 +297,6 @@ const MOCK_ORDERS: Order[] = [
|
|||||||
shippingProvince: "เชียงใหม่",
|
shippingProvince: "เชียงใหม่",
|
||||||
shippingPostcode: "50300",
|
shippingPostcode: "50300",
|
||||||
shippingCountry: "TH",
|
shippingCountry: "TH",
|
||||||
|
|
||||||
billingName: "Mint W.",
|
billingName: "Mint W.",
|
||||||
billingTaxId: null,
|
billingTaxId: null,
|
||||||
billingAddressLine1: "55/9 ถนนสายน้ำ",
|
billingAddressLine1: "55/9 ถนนสายน้ำ",
|
||||||
@@ -372,13 +305,11 @@ const MOCK_ORDERS: Order[] = [
|
|||||||
billingProvince: "เชียงใหม่",
|
billingProvince: "เชียงใหม่",
|
||||||
billingPostcode: "50300",
|
billingPostcode: "50300",
|
||||||
billingCountry: "TH",
|
billingCountry: "TH",
|
||||||
|
|
||||||
paymentMethod: "wallet",
|
paymentMethod: "wallet",
|
||||||
paymentStatus: "paid",
|
paymentStatus: "paid",
|
||||||
paymentProvider: "TikTokPay",
|
paymentProvider: "TikTokPay",
|
||||||
paymentTxId: "TTPAY_c0ffeecafe",
|
paymentTxId: "TTPAY_c0ffeecafe",
|
||||||
paymentFeeTHB: 14.9,
|
paymentFeeTHB: 14.9,
|
||||||
|
|
||||||
currency: "THB",
|
currency: "THB",
|
||||||
exchangeRate: 1,
|
exchangeRate: 1,
|
||||||
subtotalTHB: 2250,
|
subtotalTHB: 2250,
|
||||||
@@ -388,26 +319,22 @@ const MOCK_ORDERS: Order[] = [
|
|||||||
packagingFeeTHB: 10,
|
packagingFeeTHB: 10,
|
||||||
taxTHB: 0,
|
taxTHB: 0,
|
||||||
totalTHB: 2200,
|
totalTHB: 2200,
|
||||||
|
|
||||||
itemsCount: 4,
|
itemsCount: 4,
|
||||||
totalWeightGrams: 650,
|
totalWeightGrams: 650,
|
||||||
packageCount: 1,
|
packageCount: 1,
|
||||||
dimensionsCmL: 24,
|
dimensionsCmL: 24,
|
||||||
dimensionsCmW: 18,
|
dimensionsCmW: 18,
|
||||||
dimensionsCmH: 10,
|
dimensionsCmH: 10,
|
||||||
|
|
||||||
fulfillStatus: "shipped",
|
fulfillStatus: "shipped",
|
||||||
shipBy: "2025-10-10T12:00:00+07:00",
|
shipBy: "2025-10-10T12:00:00+07:00",
|
||||||
carrier: "J&T",
|
carrier: "J&T",
|
||||||
serviceLevel: "Express",
|
serviceLevel: "Express",
|
||||||
trackingNo: "JTTH123456789",
|
trackingNo: "JTTH123456789",
|
||||||
|
|
||||||
warehouseCode: "CNX-HUB",
|
warehouseCode: "CNX-HUB",
|
||||||
pickListId: "PICK-5509",
|
pickListId: "PICK-5509",
|
||||||
waveId: "WAVE-102",
|
waveId: "WAVE-102",
|
||||||
binFrom: "Z-03-02",
|
binFrom: "Z-03-02",
|
||||||
binTo: null,
|
binTo: null,
|
||||||
|
|
||||||
tags: [],
|
tags: [],
|
||||||
rmaFlag: false,
|
rmaFlag: false,
|
||||||
rmaReason: null,
|
rmaReason: null,
|
||||||
@@ -423,9 +350,6 @@ const MOCK_ORDERS: Order[] = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
/* ===========================
|
|
||||||
Helpers
|
|
||||||
=========================== */
|
|
||||||
const LS_KEY = "orders.visibleCols.v2";
|
const LS_KEY = "orders.visibleCols.v2";
|
||||||
|
|
||||||
const THB = (n: number) => `฿${n.toLocaleString()}`;
|
const THB = (n: number) => `฿${n.toLocaleString()}`;
|
||||||
@@ -452,6 +376,7 @@ function mapPaymentColor(p: PaymentStatus) {
|
|||||||
return pill("neutral");
|
return pill("neutral");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapFulfillmentColor(f: FulfillmentStatus) {
|
function mapFulfillmentColor(f: FulfillmentStatus) {
|
||||||
switch (f) {
|
switch (f) {
|
||||||
case "delivered":
|
case "delivered":
|
||||||
@@ -466,7 +391,6 @@ function mapFulfillmentColor(f: FulfillmentStatus) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// typed getter (เลี่ยง as any)
|
|
||||||
function getProp<T, K extends keyof T>(obj: T, key: K): T[K] {
|
function getProp<T, K extends keyof T>(obj: T, key: K): T[K] {
|
||||||
return obj[key];
|
return obj[key];
|
||||||
}
|
}
|
||||||
@@ -506,218 +430,88 @@ function defaultCell(v: unknown): ReactNode {
|
|||||||
return String(v);
|
return String(v);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ===========================
|
|
||||||
คอนฟิกคอลัมน์ (ไทย + กลุ่ม)
|
|
||||||
=========================== */
|
|
||||||
const ALL_COLS: Column[] = [
|
const ALL_COLS: Column[] = [
|
||||||
// เอกลักษณ์ & ช่องทาง
|
|
||||||
{ key: "id", labelTH: "ไอดี", groupTH: "เอกลักษณ์ & ช่องทาง" },
|
{ key: "id", labelTH: "ไอดี", groupTH: "เอกลักษณ์ & ช่องทาง" },
|
||||||
{ key: "orderNo", labelTH: "เลขออเดอร์", groupTH: "เอกลักษณ์ & ช่องทาง" },
|
{ key: "orderNo", labelTH: "เลขออเดอร์", groupTH: "เอกลักษณ์ & ช่องทาง" },
|
||||||
{ key: "orderRef", labelTH: "อ้างอิงภายใน", groupTH: "เอกลักษณ์ & ช่องทาง" },
|
{ key: "orderRef", labelTH: "อ้างอิงภายใน", groupTH: "เอกลักษณ์ & ช่องทาง" },
|
||||||
{ key: "channel", labelTH: "ช่องทาง", groupTH: "เอกลักษณ์ & ช่องทาง" },
|
{ key: "channel", labelTH: "ช่องทาง", groupTH: "เอกลักษณ์ & ช่องทาง" },
|
||||||
{
|
{ key: "channelOrderId", labelTH: "เลขออเดอร์ช่องทาง", groupTH: "เอกลักษณ์ & ช่องทาง" },
|
||||||
key: "channelOrderId",
|
{ key: "marketplaceShopId", labelTH: "รหัสร้าน", groupTH: "เอกลักษณ์ & ช่องทาง" },
|
||||||
labelTH: "เลขออเดอร์ช่องทาง",
|
{ key: "marketplaceShopName", labelTH: "ชื่อร้าน", groupTH: "เอกลักษณ์ & ช่องทาง" },
|
||||||
groupTH: "เอกลักษณ์ & ช่องทาง",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "marketplaceShopId",
|
|
||||||
labelTH: "รหัสร้าน",
|
|
||||||
groupTH: "เอกลักษณ์ & ช่องทาง",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "marketplaceShopName",
|
|
||||||
labelTH: "ชื่อร้าน",
|
|
||||||
groupTH: "เอกลักษณ์ & ช่องทาง",
|
|
||||||
},
|
|
||||||
|
|
||||||
// ผู้เปิดออเดอร์
|
|
||||||
{ key: "createdByName", labelTH: "ผู้เปิดออเดอร์", groupTH: "ผู้เปิดออเดอร์" },
|
{ key: "createdByName", labelTH: "ผู้เปิดออเดอร์", groupTH: "ผู้เปิดออเดอร์" },
|
||||||
{ key: "createdByEmail", labelTH: "อีเมลผู้เปิด", groupTH: "ผู้เปิดออเดอร์" },
|
{ key: "createdByEmail", labelTH: "อีเมลผู้เปิด", groupTH: "ผู้เปิดออเดอร์" },
|
||||||
{ key: "createdById", labelTH: "รหัสผู้เปิด", groupTH: "ผู้เปิดออเดอร์" },
|
{ key: "createdById", labelTH: "รหัสผู้เปิด", groupTH: "ผู้เปิดออเดอร์" },
|
||||||
|
|
||||||
// วัน–เวลา
|
|
||||||
{ key: "dateCreated", labelTH: "สร้างเมื่อ", groupTH: "วัน–เวลา", format: (v) => fmtDate(v as string | null | undefined) },
|
{ key: "dateCreated", labelTH: "สร้างเมื่อ", groupTH: "วัน–เวลา", format: (v) => fmtDate(v as string | null | undefined) },
|
||||||
{ key: "datePaid", labelTH: "ชำระเงิน", groupTH: "วัน–เวลา", format: (v) => fmtDate(v as string | null | undefined) },
|
{ key: "datePaid", labelTH: "ชำระเงิน", groupTH: "วัน–เวลา", format: (v) => fmtDate(v as string | null | undefined) },
|
||||||
{ key: "datePacked", labelTH: "แพ็คของ", groupTH: "วัน–เวลา", format: (v) => fmtDate(v as string | null | undefined) },
|
{ key: "datePacked", labelTH: "แพ็คของ", groupTH: "วัน–เวลา", format: (v) => fmtDate(v as string | null | undefined) },
|
||||||
{ key: "dateShipped", labelTH: "ส่งของ", groupTH: "วัน–เวลา", format: (v) => fmtDate(v as string | null | undefined) },
|
{ key: "dateShipped", labelTH: "ส่งของ", groupTH: "วัน–เวลา", format: (v) => fmtDate(v as string | null | undefined) },
|
||||||
{ key: "dateDelivered", labelTH: "ถึงปลายทาง", groupTH: "วัน–เวลา", format: (v) => fmtDate(v as string | null | undefined) },
|
{ key: "dateDelivered", labelTH: "ถึงปลายทาง", groupTH: "วัน–เวลา", format: (v) => fmtDate(v as string | null | undefined) },
|
||||||
{ key: "dateCancelled", labelTH: "ยกเลิก", groupTH: "วัน–เวลา", format: (v) => fmtDate(v as string | null | undefined) },
|
{ key: "dateCancelled", labelTH: "ยกเลิก", groupTH: "วัน–เวลา", format: (v) => fmtDate(v as string | null | undefined) },
|
||||||
|
|
||||||
// ลูกค้า
|
|
||||||
{ key: "customerId", labelTH: "รหัสลูกค้า", groupTH: "ลูกค้า" },
|
{ key: "customerId", labelTH: "รหัสลูกค้า", groupTH: "ลูกค้า" },
|
||||||
{ key: "customerName", labelTH: "ชื่อลูกค้า", groupTH: "ลูกค้า" },
|
{ key: "customerName", labelTH: "ชื่อลูกค้า", groupTH: "ลูกค้า" },
|
||||||
{ key: "customerEmail", labelTH: "อีเมลลูกค้า", groupTH: "ลูกค้า" },
|
{ key: "customerEmail", labelTH: "อีเมลลูกค้า", groupTH: "ลูกค้า" },
|
||||||
{ key: "customerPhone", labelTH: "เบอร์ลูกค้า", groupTH: "ลูกค้า" },
|
{ key: "customerPhone", labelTH: "เบอร์ลูกค้า", groupTH: "ลูกค้า" },
|
||||||
|
|
||||||
// ที่อยู่จัดส่ง
|
|
||||||
{ key: "shippingName", labelTH: "ชื่อผู้รับ", groupTH: "ที่อยู่จัดส่ง" },
|
{ key: "shippingName", labelTH: "ชื่อผู้รับ", groupTH: "ที่อยู่จัดส่ง" },
|
||||||
{ key: "shippingPhone", labelTH: "เบอร์ผู้รับ", groupTH: "ที่อยู่จัดส่ง" },
|
{ key: "shippingPhone", labelTH: "เบอร์ผู้รับ", groupTH: "ที่อยู่จัดส่ง" },
|
||||||
{
|
{ key: "shippingAddressLine1", labelTH: "ที่อยู่ (บรรทัด 1)", groupTH: "ที่อยู่จัดส่ง" },
|
||||||
key: "shippingAddressLine1",
|
{ key: "shippingAddressLine2", labelTH: "ที่อยู่ (บรรทัด 2)", groupTH: "ที่อยู่จัดส่ง" },
|
||||||
labelTH: "ที่อยู่ (บรรทัด 1)",
|
|
||||||
groupTH: "ที่อยู่จัดส่ง",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "shippingAddressLine2",
|
|
||||||
labelTH: "ที่อยู่ (บรรทัด 2)",
|
|
||||||
groupTH: "ที่อยู่จัดส่ง",
|
|
||||||
},
|
|
||||||
{ key: "shippingSubdistrict", labelTH: "แขวง/ตำบล", groupTH: "ที่อยู่จัดส่ง" },
|
{ key: "shippingSubdistrict", labelTH: "แขวง/ตำบล", groupTH: "ที่อยู่จัดส่ง" },
|
||||||
{ key: "shippingDistrict", labelTH: "เขต/อำเภอ", groupTH: "ที่อยู่จัดส่ง" },
|
{ key: "shippingDistrict", labelTH: "เขต/อำเภอ", groupTH: "ที่อยู่จัดส่ง" },
|
||||||
{ key: "shippingProvince", labelTH: "จังหวัด", groupTH: "ที่อยู่จัดส่ง" },
|
{ key: "shippingProvince", labelTH: "จังหวัด", groupTH: "ที่อยู่จัดส่ง" },
|
||||||
{ key: "shippingPostcode", labelTH: "รหัสไปรษณีย์", groupTH: "ที่อยู่จัดส่ง" },
|
{ key: "shippingPostcode", labelTH: "รหัสไปรษณีย์", groupTH: "ที่อยู่จัดส่ง" },
|
||||||
{ key: "shippingCountry", labelTH: "ประเทศ", groupTH: "ที่อยู่จัดส่ง" },
|
{ key: "shippingCountry", labelTH: "ประเทศ", groupTH: "ที่อยู่จัดส่ง" },
|
||||||
|
|
||||||
// ออกใบเสร็จ/ภาษี
|
|
||||||
{ key: "billingName", labelTH: "ชื่อสำหรับบิล", groupTH: "ออกใบเสร็จ/ภาษี" },
|
{ key: "billingName", labelTH: "ชื่อสำหรับบิล", groupTH: "ออกใบเสร็จ/ภาษี" },
|
||||||
{ key: "billingTaxId", labelTH: "เลขภาษี", groupTH: "ออกใบเสร็จ/ภาษี" },
|
{ key: "billingTaxId", labelTH: "เลขภาษี", groupTH: "ออกใบเสร็จ/ภาษี" },
|
||||||
{
|
{ key: "billingAddressLine1", labelTH: "ที่อยู่บิล (บรรทัด 1)", groupTH: "ออกใบเสร็จ/ภาษี" },
|
||||||
key: "billingAddressLine1",
|
|
||||||
labelTH: "ที่อยู่บิล (บรรทัด 1)",
|
|
||||||
groupTH: "ออกใบเสร็จ/ภาษี",
|
|
||||||
},
|
|
||||||
{ key: "billingSubdistrict", labelTH: "แขวง/ตำบล (บิล)", groupTH: "ออกใบเสร็จ/ภาษี" },
|
{ key: "billingSubdistrict", labelTH: "แขวง/ตำบล (บิล)", groupTH: "ออกใบเสร็จ/ภาษี" },
|
||||||
{ key: "billingDistrict", labelTH: "เขต/อำเภอ (บิล)", groupTH: "ออกใบเสร็จ/ภาษี" },
|
{ key: "billingDistrict", labelTH: "เขต/อำเภอ (บิล)", groupTH: "ออกใบเสร็จ/ภาษี" },
|
||||||
{ key: "billingProvince", labelTH: "จังหวัด (บิล)", groupTH: "ออกใบเสร็จ/ภาษี" },
|
{ key: "billingProvince", labelTH: "จังหวัด (บิล)", groupTH: "ออกใบเสร็จ/ภาษี" },
|
||||||
{
|
{ key: "billingPostcode", labelTH: "รหัสไปรษณีย์ (บิล)", groupTH: "ออกใบเสร็จ/ภาษี" },
|
||||||
key: "billingPostcode",
|
|
||||||
labelTH: "รหัสไปรษณีย์ (บิล)",
|
|
||||||
groupTH: "ออกใบเสร็จ/ภาษี",
|
|
||||||
},
|
|
||||||
{ key: "billingCountry", labelTH: "ประเทศ (บิล)", groupTH: "ออกใบเสร็จ/ภาษี" },
|
{ key: "billingCountry", labelTH: "ประเทศ (บิล)", groupTH: "ออกใบเสร็จ/ภาษี" },
|
||||||
|
|
||||||
// การชำระเงิน
|
|
||||||
{ key: "paymentMethod", labelTH: "วิธีชำระ", groupTH: "การชำระเงิน" },
|
{ key: "paymentMethod", labelTH: "วิธีชำระ", groupTH: "การชำระเงิน" },
|
||||||
{
|
{ key: "paymentStatus", labelTH: "สถานะชำระ", groupTH: "การชำระเงิน", format: (v) => (<span className={`rounded-full px-2 py-0.5 text-[11px] ${mapPaymentColor(v as PaymentStatus)}`}>{v as string}</span>) },
|
||||||
key: "paymentStatus",
|
|
||||||
labelTH: "สถานะชำระ",
|
|
||||||
groupTH: "การชำระเงิน",
|
|
||||||
format: (v) => (
|
|
||||||
<span className={`rounded-full px-2 py-0.5 text-[11px] ${mapPaymentColor(v as PaymentStatus)}`}>{v as string}</span>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{ key: "paymentProvider", labelTH: "ผู้ให้บริการชำระ", groupTH: "การชำระเงิน" },
|
{ key: "paymentProvider", labelTH: "ผู้ให้บริการชำระ", groupTH: "การชำระเงิน" },
|
||||||
{ key: "paymentTxId", labelTH: "รหัสรายการชำระ", groupTH: "การชำระเงิน" },
|
{ key: "paymentTxId", labelTH: "รหัสรายการชำระ", groupTH: "การชำระเงิน" },
|
||||||
{
|
{ key: "paymentFeeTHB", labelTH: "ค่าธรรมเนียม", groupTH: "การชำระเงิน", align: "right", format: (v) => (typeof v === "number" ? THB(v) : "-") },
|
||||||
key: "paymentFeeTHB",
|
|
||||||
labelTH: "ค่าธรรมเนียม",
|
|
||||||
groupTH: "การชำระเงิน",
|
|
||||||
align: "right",
|
|
||||||
format: (v) => (typeof v === "number" ? THB(v) : "-"),
|
|
||||||
},
|
|
||||||
|
|
||||||
// ราคา/ต้นทุนย่อย
|
|
||||||
{ key: "currency", labelTH: "สกุล", groupTH: "ราคา/ต้นทุนย่อย" },
|
{ key: "currency", labelTH: "สกุล", groupTH: "ราคา/ต้นทุนย่อย" },
|
||||||
{ key: "exchangeRate", labelTH: "เรท", groupTH: "ราคา/ต้นทุนย่อย", align: "right" },
|
{ key: "exchangeRate", labelTH: "เรท", groupTH: "ราคา/ต้นทุนย่อย", align: "right" },
|
||||||
{
|
{ key: "subtotalTHB", labelTH: "ยอดก่อนลด", groupTH: "ราคา/ต้นทุนย่อย", align: "right", format: (v) => THB(Number(v)) },
|
||||||
key: "subtotalTHB",
|
{ key: "discountTHB", labelTH: "ส่วนลด", groupTH: "ราคา/ต้นทุนย่อย", align: "right", format: (v) => THB(Number(v)) },
|
||||||
labelTH: "ยอดก่อนลด",
|
{ key: "shippingFeeTHB", labelTH: "ค่าส่ง", groupTH: "ราคา/ต้นทุนย่อย", align: "right", format: (v) => THB(Number(v)) },
|
||||||
groupTH: "ราคา/ต้นทุนย่อย",
|
{ key: "codFeeTHB", labelTH: "ค่า COD", groupTH: "ราคา/ต้นทุนย่อย", align: "right", format: (v) => THB(Number(v)) },
|
||||||
align: "right",
|
{ key: "packagingFeeTHB", labelTH: "ค่าบรรจุภัณฑ์", groupTH: "ราคา/ต้นทุนย่อย", align: "right", format: (v) => THB(Number(v)) },
|
||||||
format: (v) => THB(Number(v)),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "discountTHB",
|
|
||||||
labelTH: "ส่วนลด",
|
|
||||||
groupTH: "ราคา/ต้นทุนย่อย",
|
|
||||||
align: "right",
|
|
||||||
format: (v) => THB(Number(v)),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "shippingFeeTHB",
|
|
||||||
labelTH: "ค่าส่ง",
|
|
||||||
groupTH: "ราคา/ต้นทุนย่อย",
|
|
||||||
align: "right",
|
|
||||||
format: (v) => THB(Number(v)),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "codFeeTHB",
|
|
||||||
labelTH: "ค่า COD",
|
|
||||||
groupTH: "ราคา/ต้นทุนย่อย",
|
|
||||||
align: "right",
|
|
||||||
format: (v) => THB(Number(v)),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "packagingFeeTHB",
|
|
||||||
labelTH: "ค่าบรรจุภัณฑ์",
|
|
||||||
groupTH: "ราคา/ต้นทุนย่อย",
|
|
||||||
align: "right",
|
|
||||||
format: (v) => THB(Number(v)),
|
|
||||||
},
|
|
||||||
{ key: "taxTHB", labelTH: "ภาษี", groupTH: "ราคา/ต้นทุนย่อย", align: "right", format: (v) => THB(Number(v)) },
|
{ key: "taxTHB", labelTH: "ภาษี", groupTH: "ราคา/ต้นทุนย่อย", align: "right", format: (v) => THB(Number(v)) },
|
||||||
{ key: "totalTHB", labelTH: "ยอดรวม", groupTH: "ราคา/ต้นทุนย่อย", align: "right", format: (v) => THB(Number(v)) },
|
{ key: "totalTHB", labelTH: "ยอดรวม", groupTH: "ราคา/ต้นทุนย่อย", align: "right", format: (v) => THB(Number(v)) },
|
||||||
|
|
||||||
// สินค้า/โลจิสติกส์
|
|
||||||
{ key: "itemsCount", labelTH: "จำนวนชิ้น", groupTH: "สินค้า/โลจิสติกส์", align: "right" },
|
{ key: "itemsCount", labelTH: "จำนวนชิ้น", groupTH: "สินค้า/โลจิสติกส์", align: "right" },
|
||||||
{ key: "totalWeightGrams", labelTH: "น้ำหนัก(g)", groupTH: "สินค้า/โลจิสติกส์", align: "right" },
|
{ key: "totalWeightGrams", labelTH: "น้ำหนัก(g)", groupTH: "สินค้า/โลจิสติกส์", align: "right" },
|
||||||
{ key: "packageCount", labelTH: "จำนวนพัสดุ", groupTH: "สินค้า/โลจิสติกส์", align: "right" },
|
{ key: "packageCount", labelTH: "จำนวนพัสดุ", groupTH: "สินค้า/โลจิสติกส์", align: "right" },
|
||||||
{ key: "dimensionsCmL", labelTH: "ยาว(cm)", groupTH: "สินค้า/โลจิสติกส์", align: "right" },
|
{ key: "dimensionsCmL", labelTH: "ยาว(cm)", groupTH: "สินค้า/โลจิสติกส์", align: "right" },
|
||||||
{ key: "dimensionsCmW", labelTH: "กว้าง(cm)", groupTH: "สินค้า/โลจิสติกส์", align: "right" },
|
{ key: "dimensionsCmW", labelTH: "กว้าง(cm)", groupTH: "สินค้า/โลจิสติกส์", align: "right" },
|
||||||
{ key: "dimensionsCmH", labelTH: "สูง(cm)", groupTH: "สินค้า/โลจิสติกส์", align: "right" },
|
{ key: "dimensionsCmH", labelTH: "สูง(cm)", groupTH: "สินค้า/โลจิสติกส์", align: "right" },
|
||||||
|
{ key: "fulfillStatus", labelTH: "สถานะจัดส่ง", groupTH: "สถานะจัดส่ง", format: (v) => (<span className={`rounded-full px-2 py-0.5 text-[11px] ${mapFulfillmentColor(v as FulfillmentStatus)}`}>{v as string}</span>) },
|
||||||
// สถานะจัดส่ง
|
|
||||||
{
|
|
||||||
key: "fulfillStatus",
|
|
||||||
labelTH: "สถานะจัดส่ง",
|
|
||||||
groupTH: "สถานะจัดส่ง",
|
|
||||||
format: (v) => (
|
|
||||||
<span className={`rounded-full px-2 py-0.5 text-[11px] ${mapFulfillmentColor(v as FulfillmentStatus)}`}>{v as string}</span>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{ key: "shipBy", labelTH: "กำหนดส่งภายใน", groupTH: "สถานะจัดส่ง", format: (v) => fmtDate(v as string | null | undefined) },
|
{ key: "shipBy", labelTH: "กำหนดส่งภายใน", groupTH: "สถานะจัดส่ง", format: (v) => fmtDate(v as string | null | undefined) },
|
||||||
{ key: "carrier", labelTH: "ขนส่ง", groupTH: "สถานะจัดส่ง" },
|
{ key: "carrier", labelTH: "ขนส่ง", groupTH: "สถานะจัดส่ง" },
|
||||||
{ key: "serviceLevel", labelTH: "บริการ", groupTH: "สถานะจัดส่ง" },
|
{ key: "serviceLevel", labelTH: "บริการ", groupTH: "สถานะจัดส่ง" },
|
||||||
{ key: "trackingNo", labelTH: "เลขติดตาม", groupTH: "สถานะจัดส่ง" },
|
{ key: "trackingNo", labelTH: "เลขติดตาม", groupTH: "สถานะจัดส่ง" },
|
||||||
|
|
||||||
// โกดัง/ปฏิบัติการ
|
|
||||||
{ key: "warehouseCode", labelTH: "คลังสินค้า", groupTH: "โกดัง/ปฏิบัติการ" },
|
{ key: "warehouseCode", labelTH: "คลังสินค้า", groupTH: "โกดัง/ปฏิบัติการ" },
|
||||||
{ key: "pickListId", labelTH: "ใบหยิบ", groupTH: "โกดัง/ปฏิบัติการ" },
|
{ key: "pickListId", labelTH: "ใบหยิบ", groupTH: "โกดัง/ปฏิบัติการ" },
|
||||||
{ key: "waveId", labelTH: "รหัสเวฟ", groupTH: "โกดัง/ปฏิบัติการ" },
|
{ key: "waveId", labelTH: "รหัสเวฟ", groupTH: "โกดัง/ปฏิบัติการ" },
|
||||||
{ key: "binFrom", labelTH: "หยิบจากช่อง", groupTH: "โกดัง/ปฏิบัติการ" },
|
{ key: "binFrom", labelTH: "หยิบจากช่อง", groupTH: "โกดัง/ปฏิบัติการ" },
|
||||||
{ key: "binTo", labelTH: "ย้ายไปช่อง", groupTH: "โกดัง/ปฏิบัติการ" },
|
{ key: "binTo", labelTH: "ย้ายไปช่อง", groupTH: "โกดัง/ปฏิบัติการ" },
|
||||||
|
{ key: "tags", labelTH: "แท็ก", groupTH: "ธง/โน้ต/ความเสี่ยง", format: (v) => (Array.isArray(v) && v.length ? v.join("|") : "-") },
|
||||||
// ธง/โน้ต/ความเสี่ยง
|
{ key: "rmaFlag", labelTH: "มีเคลม?", groupTH: "ธง/โน้ต/ความเสี่ยง", format: (v) => (typeof v === "boolean" ? (v ? "YES" : "NO") : "-") },
|
||||||
{
|
|
||||||
key: "tags",
|
|
||||||
labelTH: "แท็ก",
|
|
||||||
groupTH: "ธง/โน้ต/ความเสี่ยง",
|
|
||||||
format: (v) => (Array.isArray(v) && v.length ? v.join("|") : "-"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "rmaFlag",
|
|
||||||
labelTH: "มีเคลม?",
|
|
||||||
groupTH: "ธง/โน้ต/ความเสี่ยง",
|
|
||||||
format: (v) => (typeof v === "boolean" ? (v ? "YES" : "NO") : "-"),
|
|
||||||
},
|
|
||||||
{ key: "rmaReason", labelTH: "สาเหตุเคลม", groupTH: "ธง/โน้ต/ความเสี่ยง" },
|
{ key: "rmaReason", labelTH: "สาเหตุเคลม", groupTH: "ธง/โน้ต/ความเสี่ยง" },
|
||||||
{ key: "noteSeller", labelTH: "โน้ตผู้ขาย", groupTH: "ธง/โน้ต/ความเสี่ยง" },
|
{ key: "noteSeller", labelTH: "โน้ตผู้ขาย", groupTH: "ธง/โน้ต/ความเสี่ยง" },
|
||||||
{ key: "noteBuyer", labelTH: "โน้ตผู้ซื้อ", groupTH: "ธง/โน้ต/ความเสี่ยง" },
|
{ key: "noteBuyer", labelTH: "โน้ตผู้ซื้อ", groupTH: "ธง/โน้ต/ความเสี่ยง" },
|
||||||
{
|
{ key: "slaShipOnTime", labelTH: "ส่งทันเวลา?", groupTH: "ธง/โน้ต/ความเสี่ยง", format: (v) => (typeof v === "boolean" ? (v ? "OK" : "N/A") : "N/A") },
|
||||||
key: "slaShipOnTime",
|
{ key: "giftWrap", labelTH: "ห่อของขวัญ", groupTH: "ธง/โน้ต/ความเสี่ยง", format: (v) => (typeof v === "boolean" ? (v ? "YES" : "NO") : "NO") },
|
||||||
labelTH: "ส่งทันเวลา?",
|
{ key: "fragile", labelTH: "แตกหักง่าย", groupTH: "ธง/โน้ต/ความเสี่ยง", format: (v) => (typeof v === "boolean" ? (v ? "YES" : "NO") : "NO") },
|
||||||
groupTH: "ธง/โน้ต/ความเสี่ยง",
|
|
||||||
format: (v) => (typeof v === "boolean" ? (v ? "OK" : "N/A") : "N/A"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "giftWrap",
|
|
||||||
labelTH: "ห่อของขวัญ",
|
|
||||||
groupTH: "ธง/โน้ต/ความเสี่ยง",
|
|
||||||
format: (v) => (typeof v === "boolean" ? (v ? "YES" : "NO") : "NO"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "fragile",
|
|
||||||
labelTH: "แตกหักง่าย",
|
|
||||||
groupTH: "ธง/โน้ต/ความเสี่ยง",
|
|
||||||
format: (v) => (typeof v === "boolean" ? (v ? "YES" : "NO") : "NO"),
|
|
||||||
},
|
|
||||||
{ key: "priorityLevel", labelTH: "ลำดับความสำคัญ", groupTH: "ธง/โน้ต/ความเสี่ยง", align: "right" },
|
{ key: "priorityLevel", labelTH: "ลำดับความสำคัญ", groupTH: "ธง/โน้ต/ความเสี่ยง", align: "right" },
|
||||||
{ key: "riskScore", labelTH: "คะแนนเสี่ยง", groupTH: "ธง/โน้ต/ความเสี่ยง", align: "right" },
|
{ key: "riskScore", labelTH: "คะแนนเสี่ยง", groupTH: "ธง/โน้ต/ความเสี่ยง", align: "right" },
|
||||||
{ key: "holdReason", labelTH: "เหตุผลพักออเดอร์", groupTH: "ธง/โน้ต/ความเสี่ยง" },
|
{ key: "holdReason", labelTH: "เหตุผลพักออเดอร์", groupTH: "ธง/โน้ต/ความเสี่ยง" },
|
||||||
{ key: "fraudCheckStatus", labelTH: "สถานะ Fraud", groupTH: "ธง/โน้ต/ความเสี่ยง" },
|
{ key: "fraudCheckStatus", labelTH: "สถานะ Fraud", groupTH: "ธง/โน้ต/ความเสี่ยง" },
|
||||||
];
|
];
|
||||||
|
|
||||||
// ค่าตั้งต้นคอลัมน์ที่แสดง
|
|
||||||
const DEFAULT_VISIBLE: ColumnKey[] = [
|
const DEFAULT_VISIBLE: ColumnKey[] = [
|
||||||
"orderNo",
|
"orderNo",
|
||||||
"dateCreated",
|
"dateCreated",
|
||||||
@@ -734,106 +528,30 @@ const DEFAULT_VISIBLE: ColumnKey[] = [
|
|||||||
"warehouseCode",
|
"warehouseCode",
|
||||||
];
|
];
|
||||||
|
|
||||||
/* ===========================
|
|
||||||
จัดกลุ่มสำหรับ Modal (ไทย)
|
|
||||||
=========================== */
|
|
||||||
const GROUPS: { titleTH: string; keys: ColumnKey[] }[] = [
|
const GROUPS: { titleTH: string; keys: ColumnKey[] }[] = [
|
||||||
{
|
{ titleTH: "เอกลักษณ์ & ช่องทาง", keys: ["id", "orderNo", "orderRef", "channel", "channelOrderId", "marketplaceShopId", "marketplaceShopName"] },
|
||||||
titleTH: "เอกลักษณ์ & ช่องทาง",
|
{ titleTH: "ผู้เปิดออเดอร์", keys: ["createdByName", "createdByEmail", "createdById"] },
|
||||||
keys: ["id", "orderNo", "orderRef", "channel", "channelOrderId", "marketplaceShopId", "marketplaceShopName"],
|
{ titleTH: "วัน–เวลา", keys: ["dateCreated", "datePaid", "datePacked", "dateShipped", "dateDelivered", "dateCancelled"] },
|
||||||
},
|
{ titleTH: "ลูกค้า", keys: ["customerId", "customerName", "customerEmail", "customerPhone"] },
|
||||||
{
|
{ titleTH: "ที่อยู่จัดส่ง", keys: ["shippingName", "shippingPhone", "shippingAddressLine1", "shippingAddressLine2", "shippingSubdistrict", "shippingDistrict", "shippingProvince", "shippingPostcode", "shippingCountry"] },
|
||||||
titleTH: "ผู้เปิดออเดอร์",
|
{ titleTH: "ออกใบเสร็จ/ภาษี", keys: ["billingName", "billingTaxId", "billingAddressLine1", "billingSubdistrict", "billingDistrict", "billingProvince", "billingPostcode", "billingCountry"] },
|
||||||
keys: ["createdByName", "createdByEmail", "createdById"],
|
{ titleTH: "การชำระเงิน", keys: ["paymentMethod", "paymentStatus", "paymentProvider", "paymentTxId", "paymentFeeTHB"] },
|
||||||
},
|
{ titleTH: "ราคา/ต้นทุนย่อย", keys: ["currency", "exchangeRate", "subtotalTHB", "discountTHB", "shippingFeeTHB", "codFeeTHB", "packagingFeeTHB", "taxTHB", "totalTHB"] },
|
||||||
{
|
{ titleTH: "สินค้า/โลจิสติกส์", keys: ["itemsCount", "totalWeightGrams", "packageCount", "dimensionsCmL", "dimensionsCmW", "dimensionsCmH"] },
|
||||||
titleTH: "วัน–เวลา",
|
{ titleTH: "สถานะจัดส่ง", keys: ["fulfillStatus", "shipBy", "carrier", "serviceLevel", "trackingNo"] },
|
||||||
keys: ["dateCreated", "datePaid", "datePacked", "dateShipped", "dateDelivered", "dateCancelled"],
|
{ titleTH: "โกดัง/ปฏิบัติการ", keys: ["warehouseCode", "pickListId", "waveId", "binFrom", "binTo"] },
|
||||||
},
|
{ titleTH: "ธง/โน้ต/ความเสี่ยง", keys: ["tags", "rmaFlag", "rmaReason", "noteSeller", "noteBuyer", "slaShipOnTime", "giftWrap", "fragile", "priorityLevel", "riskScore", "holdReason", "fraudCheckStatus"] },
|
||||||
{
|
|
||||||
titleTH: "ลูกค้า",
|
|
||||||
keys: ["customerId", "customerName", "customerEmail", "customerPhone"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
titleTH: "ที่อยู่จัดส่ง",
|
|
||||||
keys: [
|
|
||||||
"shippingName",
|
|
||||||
"shippingPhone",
|
|
||||||
"shippingAddressLine1",
|
|
||||||
"shippingAddressLine2",
|
|
||||||
"shippingSubdistrict",
|
|
||||||
"shippingDistrict",
|
|
||||||
"shippingProvince",
|
|
||||||
"shippingPostcode",
|
|
||||||
"shippingCountry",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
titleTH: "ออกใบเสร็จ/ภาษี",
|
|
||||||
keys: [
|
|
||||||
"billingName",
|
|
||||||
"billingTaxId",
|
|
||||||
"billingAddressLine1",
|
|
||||||
"billingSubdistrict",
|
|
||||||
"billingDistrict",
|
|
||||||
"billingProvince",
|
|
||||||
"billingPostcode",
|
|
||||||
"billingCountry",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
titleTH: "การชำระเงิน",
|
|
||||||
keys: ["paymentMethod", "paymentStatus", "paymentProvider", "paymentTxId", "paymentFeeTHB"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
titleTH: "ราคา/ต้นทุนย่อย",
|
|
||||||
keys: ["currency", "exchangeRate", "subtotalTHB", "discountTHB", "shippingFeeTHB", "codFeeTHB", "packagingFeeTHB", "taxTHB", "totalTHB"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
titleTH: "สินค้า/โลจิสติกส์",
|
|
||||||
keys: ["itemsCount", "totalWeightGrams", "packageCount", "dimensionsCmL", "dimensionsCmW", "dimensionsCmH"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
titleTH: "สถานะจัดส่ง",
|
|
||||||
keys: ["fulfillStatus", "shipBy", "carrier", "serviceLevel", "trackingNo"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
titleTH: "โกดัง/ปฏิบัติการ",
|
|
||||||
keys: ["warehouseCode", "pickListId", "waveId", "binFrom", "binTo"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
titleTH: "ธง/โน้ต/ความเสี่ยง",
|
|
||||||
keys: [
|
|
||||||
"tags",
|
|
||||||
"rmaFlag",
|
|
||||||
"rmaReason",
|
|
||||||
"noteSeller",
|
|
||||||
"noteBuyer",
|
|
||||||
"slaShipOnTime",
|
|
||||||
"giftWrap",
|
|
||||||
"fragile",
|
|
||||||
"priorityLevel",
|
|
||||||
"riskScore",
|
|
||||||
"holdReason",
|
|
||||||
"fraudCheckStatus",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
/* ===========================
|
|
||||||
Page
|
|
||||||
=========================== */
|
|
||||||
export default function OrdersPage() {
|
export default function OrdersPage() {
|
||||||
const [q, setQ] = useState("");
|
const [q, setQ] = useState("");
|
||||||
const [channel, setChannel] = useState<"all" | Channel>("all");
|
const [channel, setChannel] = useState<"all" | Channel>("all");
|
||||||
const [payment, setPayment] = useState<"all" | PaymentStatus>("all");
|
const [payment, setPayment] = useState<"all" | PaymentStatus>("all");
|
||||||
const [fulfillment, setFulfillment] = useState<"all" | FulfillmentStatus>("all");
|
const [fulfillment, setFulfillment] = useState<"all" | FulfillmentStatus>("all");
|
||||||
|
|
||||||
const [visibleKeys, setVisibleKeys] = useState<ColumnKey[]>(DEFAULT_VISIBLE);
|
const [visibleKeys, setVisibleKeys] = useState<ColumnKey[]>(DEFAULT_VISIBLE);
|
||||||
const [openCustomize, setOpenCustomize] = useState(false);
|
const [openCustomize, setOpenCustomize] = useState(false);
|
||||||
const [tempKeys, setTempKeys] = useState<ColumnKey[]>(DEFAULT_VISIBLE);
|
const [tempKeys, setTempKeys] = useState<ColumnKey[]>(DEFAULT_VISIBLE);
|
||||||
|
|
||||||
// โหลด/จำคอลัมน์ที่เลือก
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
try {
|
try {
|
||||||
const raw = localStorage.getItem(LS_KEY);
|
const raw = localStorage.getItem(LS_KEY);
|
||||||
@@ -853,7 +571,6 @@ export default function OrdersPage() {
|
|||||||
} catch {}
|
} catch {}
|
||||||
};
|
};
|
||||||
|
|
||||||
// กรองรายการ
|
|
||||||
const rows = useMemo(() => {
|
const rows = useMemo(() => {
|
||||||
let arr = [...MOCK_ORDERS];
|
let arr = [...MOCK_ORDERS];
|
||||||
if (channel !== "all") arr = arr.filter((o) => o.channel === channel);
|
if (channel !== "all") arr = arr.filter((o) => o.channel === channel);
|
||||||
@@ -878,18 +595,15 @@ export default function OrdersPage() {
|
|||||||
.some((x) => x.toLowerCase().includes(s)),
|
.some((x) => x.toLowerCase().includes(s)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// ใหม่สุดก่อน
|
|
||||||
arr.sort((a, b) => +new Date(b.dateCreated) - +new Date(a.dateCreated));
|
arr.sort((a, b) => +new Date(b.dateCreated) - +new Date(a.dateCreated));
|
||||||
return arr;
|
return arr;
|
||||||
}, [q, channel, payment, fulfillment]);
|
}, [q, channel, payment, fulfillment]);
|
||||||
|
|
||||||
// สร้างคอลัมน์ที่แสดงจริง
|
|
||||||
const visibleCols: Column[] = useMemo(() => {
|
const visibleCols: Column[] = useMemo(() => {
|
||||||
const map = new Map(ALL_COLS.map((c) => [c.key, c]));
|
const map = new Map(ALL_COLS.map((c) => [c.key, c]));
|
||||||
return visibleKeys.map((k) => map.get(k)!).filter(Boolean);
|
return visibleKeys.map((k) => map.get(k)!).filter(Boolean);
|
||||||
}, [visibleKeys]);
|
}, [visibleKeys]);
|
||||||
|
|
||||||
// สร้างหัวตาราง 2 ชั้น: ชั้นบน = กลุ่ม, ชั้นล่าง = หัวคอลัมน์ย่อย
|
|
||||||
const topHeaderSegments = useMemo(() => {
|
const topHeaderSegments = useMemo(() => {
|
||||||
type Seg = { groupTH: string; colSpan: number };
|
type Seg = { groupTH: string; colSpan: number };
|
||||||
const segs: Seg[] = [];
|
const segs: Seg[] = [];
|
||||||
@@ -905,8 +619,8 @@ export default function OrdersPage() {
|
|||||||
}, [visibleCols]);
|
}, [visibleCols]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6 overflow-x-clip">
|
<div className="space-y-6">
|
||||||
{/* ส่วนหัว */}
|
{/* Header */}
|
||||||
<div className="flex flex-col gap-3 sm:flex-row sm:items-end sm:justify-between">
|
<div className="flex flex-col gap-3 sm:flex-row sm:items-end sm:justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-3xl font-extrabold tracking-tight">ออเดอร์</h1>
|
<h1 className="text-3xl font-extrabold tracking-tight">ออเดอร์</h1>
|
||||||
@@ -933,8 +647,8 @@ export default function OrdersPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* ตัวกรอง */}
|
{/* Filters — ใช้ p-5 ให้เท่ากันทุกการ์ด */}
|
||||||
<section className="rounded-3xl border border-neutral-200/70 bg-white/80 p-4 shadow-[0_10px_30px_-12px_rgba(0,0,0,0.08)]">
|
<section className="rounded-3xl border border-neutral-200/70 bg-white/80 p-5 shadow-[0_10px_30px_-12px_rgba(0,0,0,0.08)]">
|
||||||
<div className="grid gap-3 sm:grid-cols-12">
|
<div className="grid gap-3 sm:grid-cols-12">
|
||||||
<div className="sm:col-span-6">
|
<div className="sm:col-span-6">
|
||||||
<input
|
<input
|
||||||
@@ -944,8 +658,6 @@ export default function OrdersPage() {
|
|||||||
className="w-full rounded-xl border border-neutral-200/70 bg-white/70 px-3 py-2 text-sm shadow-sm outline-none placeholder:text-neutral-400 focus:border-neutral-300"
|
className="w-full rounded-xl border border-neutral-200/70 bg-white/70 px-3 py-2 text-sm shadow-sm outline-none placeholder:text-neutral-400 focus:border-neutral-300"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* ช่องทาง (Select) */}
|
|
||||||
<div className="sm:col-span-2">
|
<div className="sm:col-span-2">
|
||||||
<select
|
<select
|
||||||
value={channel}
|
value={channel}
|
||||||
@@ -960,8 +672,6 @@ export default function OrdersPage() {
|
|||||||
<option value="chat">แชต</option>
|
<option value="chat">แชต</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* การชำระเงิน */}
|
|
||||||
<div className="sm:col-span-2">
|
<div className="sm:col-span-2">
|
||||||
<select
|
<select
|
||||||
value={payment}
|
value={payment}
|
||||||
@@ -976,8 +686,6 @@ export default function OrdersPage() {
|
|||||||
<option value="refunded">คืนเงิน (refunded)</option>
|
<option value="refunded">คืนเงิน (refunded)</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* สถานะจัดส่ง */}
|
|
||||||
<div className="sm:col-span-2">
|
<div className="sm:col-span-2">
|
||||||
<select
|
<select
|
||||||
value={fulfillment}
|
value={fulfillment}
|
||||||
@@ -996,82 +704,80 @@ export default function OrdersPage() {
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* ตาราง (2 ชั้น) — ไม่ fix size: ใช้ table-auto, ให้คอนเทนต์กำหนดความกว้าง, บรรทัดเดียวทุกเซลล์ */}
|
{/* Table — ดึงสกรอลบาร์ออกนอก padding ด้วย -mx-5 แล้วชดเชย px-5 */}
|
||||||
<section className="rounded-3xl border border-neutral-200/70 bg-white/80 p-5 shadow-[0_10px_30px_-12px_rgba(0,0,0,0.08)]">
|
<section className="rounded-3xl border border-neutral-200/70 bg-white/80 p-5 shadow-[0_10px_30px_-12px_rgba(0,0,0,0.08)]">
|
||||||
<div className="w-full max-w-full overflow-x-auto">
|
<div className="-mx-5 overflow-x-auto">
|
||||||
<table className="table-auto text-sm">
|
<div className="px-5">
|
||||||
<thead>
|
<table className="min-w-full table-auto text-sm">
|
||||||
{/* ชั้นบน: กลุ่มหัวข้อ (ไทย) */}
|
<thead>
|
||||||
<tr className="text-neutral-600">
|
<tr className="text-neutral-600">
|
||||||
{topHeaderSegments.map((seg, idx) => (
|
{topHeaderSegments.map((seg, idx) => (
|
||||||
<th
|
<th
|
||||||
key={`${seg.groupTH}-${idx}`}
|
key={`${seg.groupTH}-${idx}`}
|
||||||
colSpan={seg.colSpan}
|
colSpan={seg.colSpan}
|
||||||
className="pb-1 pr-4 text-center text-[12px] font-semibold whitespace-nowrap"
|
className="pb-1 pr-4 whitespace-nowrap text-center text-[12px] font-semibold"
|
||||||
>
|
>
|
||||||
{seg.groupTH}
|
{seg.groupTH}
|
||||||
</th>
|
</th>
|
||||||
))}
|
))}
|
||||||
</tr>
|
|
||||||
{/* ชั้นล่าง: หัวคอลัมน์ย่อย (ไทย) */}
|
|
||||||
<tr className="text-left text-neutral-500">
|
|
||||||
{visibleCols.map((c) => (
|
|
||||||
<th
|
|
||||||
key={String(c.key)}
|
|
||||||
className={`pb-2 pr-4 whitespace-nowrap ${c.align === "right" ? "text-right" : ""}`}
|
|
||||||
>
|
|
||||||
{c.labelTH}
|
|
||||||
</th>
|
|
||||||
))}
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
|
|
||||||
<tbody className="divide-y divide-neutral-200/70">
|
|
||||||
{rows.map((o) => (
|
|
||||||
<tr key={o.id} className="align-middle">
|
|
||||||
{visibleCols.map((c) => {
|
|
||||||
const raw = getProp(o, c.key);
|
|
||||||
const content: ReactNode = c.format ? c.format(raw, o) : defaultCell(raw);
|
|
||||||
const align = c.align === "right" ? "text-right" : "";
|
|
||||||
return (
|
|
||||||
<td key={`${o.id}-${String(c.key)}`} className={`py-2 pr-4 whitespace-nowrap ${align}`}>
|
|
||||||
{c.key === "orderNo" ? (
|
|
||||||
<a
|
|
||||||
href={`/orders/${o.id}`}
|
|
||||||
className="text-neutral-900 underline decoration-neutral-200 underline-offset-4 hover:decoration-neutral-400"
|
|
||||||
>
|
|
||||||
{content}
|
|
||||||
</a>
|
|
||||||
) : c.key === "tags" && Array.isArray(o.tags) ? (
|
|
||||||
// ไม่ให้ขึ้นหลายบรรทัด: ตัด flex-wrap ออก
|
|
||||||
<div className="flex gap-1">
|
|
||||||
{o.tags!.length ? (
|
|
||||||
o.tags!.map((t) => (
|
|
||||||
<span
|
|
||||||
key={t}
|
|
||||||
className="rounded-full border border-neutral-200 bg-neutral-50 px-2 py-0.5 text-[11px] text-neutral-700"
|
|
||||||
>
|
|
||||||
{t}
|
|
||||||
</span>
|
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<span>-</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
content
|
|
||||||
)}
|
|
||||||
</td>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
<tr className="text-left text-neutral-500">
|
||||||
</tbody>
|
{visibleCols.map((c) => (
|
||||||
</table>
|
<th
|
||||||
|
key={String(c.key)}
|
||||||
|
className={`pb-2 pr-4 whitespace-nowrap ${c.align === "right" ? "text-right" : ""}`}
|
||||||
|
>
|
||||||
|
{c.labelTH}
|
||||||
|
</th>
|
||||||
|
))}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tbody className="divide-y divide-neutral-200/70">
|
||||||
|
{rows.map((o) => (
|
||||||
|
<tr key={o.id} className="align-middle">
|
||||||
|
{visibleCols.map((c) => {
|
||||||
|
const raw = getProp(o, c.key);
|
||||||
|
const content: ReactNode = c.format ? c.format(raw, o) : defaultCell(raw);
|
||||||
|
const align = c.align === "right" ? "text-right" : "";
|
||||||
|
return (
|
||||||
|
<td key={`${o.id}-${String(c.key)}`} className={`whitespace-nowrap py-2 pr-4 ${align}`}>
|
||||||
|
{c.key === "orderNo" ? (
|
||||||
|
<a
|
||||||
|
href={`/orders/${o.id}`}
|
||||||
|
className="text-neutral-900 underline decoration-neutral-200 underline-offset-4 hover:decoration-neutral-400"
|
||||||
|
>
|
||||||
|
{content}
|
||||||
|
</a>
|
||||||
|
) : c.key === "tags" && Array.isArray(o.tags) ? (
|
||||||
|
<div className="flex gap-1">
|
||||||
|
{o.tags!.length ? (
|
||||||
|
o.tags!.map((t) => (
|
||||||
|
<span
|
||||||
|
key={t}
|
||||||
|
className="rounded-full border border-neutral-200 bg-neutral-50 px-2 py-0.5 text-[11px] text-neutral-700"
|
||||||
|
>
|
||||||
|
{t}
|
||||||
|
</span>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<span>-</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
content
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Modal: ปรับแต่งตาราง (ทำให้ scroll ได้ + footer ติดล่าง) */}
|
|
||||||
{openCustomize && (
|
{openCustomize && (
|
||||||
<div
|
<div
|
||||||
className="fixed inset-0 z-40 flex items-end justify-center bg-black/40 p-3 sm:items-center"
|
className="fixed inset-0 z-40 flex items-end justify-center bg-black/40 p-3 sm:items-center"
|
||||||
@@ -1081,8 +787,7 @@ export default function OrdersPage() {
|
|||||||
className="flex h-[90vh] max-h-[90vh] w-full max-w-5xl flex-col overflow-hidden rounded-3xl border border-neutral-200/70 bg-white shadow-2xl"
|
className="flex h-[90vh] max-h-[90vh] w-full max-w-5xl flex-col overflow-hidden rounded-3xl border border-neutral-200/70 bg-white shadow-2xl"
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
{/* Header (ติดบน) */}
|
<div className="flex shrink-0 items-center justify-between border-b border-neutral-200/70 px-5 py-4">
|
||||||
<div className="shrink-0 flex items-center justify-between border-b border-neutral-200/70 px-5 py-4">
|
|
||||||
<h3 className="text-lg font-semibold tracking-tight">ปรับแต่งตาราง</h3>
|
<h3 className="text-lg font-semibold tracking-tight">ปรับแต่งตาราง</h3>
|
||||||
<button
|
<button
|
||||||
onClick={() => setOpenCustomize(false)}
|
onClick={() => setOpenCustomize(false)}
|
||||||
@@ -1092,7 +797,6 @@ export default function OrdersPage() {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Body (เลื่อนลงได้) */}
|
|
||||||
<div className="grow overflow-y-auto p-5">
|
<div className="grow overflow-y-auto p-5">
|
||||||
<div className="mb-4 flex flex-wrap items-center gap-2">
|
<div className="mb-4 flex flex-wrap items-center gap-2">
|
||||||
<button
|
<button
|
||||||
@@ -1162,9 +866,10 @@ export default function OrdersPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Footer (ติดล่าง) */}
|
<div className="flex shrink-0 items-center justify-between border-t border-neutral-200/70 px-5 py-4">
|
||||||
<div className="shrink-0 flex items-center justify-between border-t border-neutral-200/70 px-5 py-4">
|
<div className="text-xs text-neutral-500">
|
||||||
<div className="text-xs text-neutral-500">เคล็ดลับ: ระบบจะจำคอลัมน์ที่คุณเลือกไว้ในเบราว์เซอร์นี้</div>
|
เคล็ดลับ: ระบบจะจำคอลัมน์ที่คุณเลือกไว้ในเบราว์เซอร์นี้
|
||||||
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<button
|
<button
|
||||||
onClick={() => setOpenCustomize(false)}
|
onClick={() => setOpenCustomize(false)}
|
||||||
|
|||||||
Reference in New Issue
Block a user