Fix Layout
This commit is contained in:
@@ -245,7 +245,7 @@ const MOCK_ORDERS: Order[] = [
|
|||||||
dimensionsCmW: 12,
|
dimensionsCmW: 12,
|
||||||
dimensionsCmH: 8,
|
dimensionsCmH: 8,
|
||||||
fulfillStatus: "unfulfilled",
|
fulfillStatus: "unfulfilled",
|
||||||
shipBy: "2025-10-09T18:00+07:00",
|
shipBy: "2025-10-09T18:00:00+07:00",
|
||||||
carrier: "Flash",
|
carrier: "Flash",
|
||||||
serviceLevel: "COD-Standard",
|
serviceLevel: "COD-Standard",
|
||||||
trackingNo: null,
|
trackingNo: null,
|
||||||
@@ -409,9 +409,7 @@ function downloadCsv(filename: string, rows: Order[], cols: Column[]) {
|
|||||||
const csv =
|
const csv =
|
||||||
headers.join(",") +
|
headers.join(",") +
|
||||||
"\n" +
|
"\n" +
|
||||||
rows
|
rows.map((r) => keys.map((k) => escapeCsv(getProp(r, k))).join(",")).join("\n");
|
||||||
.map((r) => keys.map((k) => escapeCsv(getProp(r, k))).join(","))
|
|
||||||
.join("\n");
|
|
||||||
const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" });
|
const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" });
|
||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
const a = document.createElement("a");
|
const a = document.createElement("a");
|
||||||
@@ -619,281 +617,294 @@ export default function OrdersPage() {
|
|||||||
}, [visibleCols]);
|
}, [visibleCols]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<>
|
||||||
{/* Header */}
|
{/* ทำให้กว้างหน้า "นิ่งเท่ากัน" เสมอ โดยจองที่สกรอลล์บาร์ */}
|
||||||
<div className="flex flex-col gap-3 sm:flex-row sm:items-end sm:justify-between">
|
<style jsx global>{`
|
||||||
<div>
|
html { scrollbar-gutter: stable both-edges; }
|
||||||
<h1 className="text-3xl font-extrabold tracking-tight">ออเดอร์</h1>
|
@supports not (scrollbar-gutter: stable both-edges) {
|
||||||
<p className="mt-1 text-sm text-neutral-500">
|
html { overflow-y: scroll; }
|
||||||
มุมมองเต็ม • แสดง {visibleCols.length} ฟิลด์ • ทั้งหมด {rows.length} ออเดอร์
|
}
|
||||||
</p>
|
`}</style>
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<button
|
|
||||||
onClick={() => setOpenCustomize(true)}
|
|
||||||
className="rounded-xl border border-neutral-200/70 bg-white/70 px-3 py-1.5 text-sm shadow-sm hover:bg-neutral-100/80"
|
|
||||||
>
|
|
||||||
ปรับแต่งตาราง
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={() => downloadCsv("orders_visible.csv", rows, visibleCols)}
|
|
||||||
className="rounded-xl border border-neutral-200/70 bg-white/70 px-3 py-1.5 text-sm shadow-sm hover:bg-neutral-100/80"
|
|
||||||
>
|
|
||||||
ส่งออก CSV (เฉพาะคอลัมน์ที่แสดง)
|
|
||||||
</button>
|
|
||||||
<button className="rounded-xl bg-neutral-900 px-3 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-neutral-800">
|
|
||||||
สร้างออเดอร์
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Filters — ใช้ p-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)]">
|
<div className="mx-auto w-full max-w-7xl px-6 space-y-6">
|
||||||
<div className="grid gap-3 sm:grid-cols-12">
|
{/* Header */}
|
||||||
<div className="sm:col-span-6">
|
<div className="flex flex-col gap-3 sm:flex-row sm:items-end sm:justify-between">
|
||||||
<input
|
<div>
|
||||||
placeholder="ค้นหา: เลขออเดอร์ / อ้างอิง / ลูกค้า / เบอร์ / อีเมล / ผู้เปิด / เลขภาษี / Tracking…"
|
<h1 className="text-3xl font-extrabold tracking-tight">ออเดอร์</h1>
|
||||||
value={q}
|
<p className="mt-1 text-sm text-neutral-500">
|
||||||
onChange={(e) => setQ(e.target.value)}
|
มุมมองเต็ม • แสดง {visibleCols.length} ฟิลด์ • ทั้งหมด {rows.length} ออเดอร์
|
||||||
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"
|
</p>
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="sm:col-span-2">
|
<div className="flex items-center gap-2">
|
||||||
<select
|
<button
|
||||||
value={channel}
|
onClick={() => setOpenCustomize(true)}
|
||||||
onChange={(e) => setChannel(e.target.value as "all" | Channel)}
|
className="rounded-xl border border-neutral-200/70 bg-white/70 px-3 py-1.5 text-sm shadow-sm hover:bg-neutral-100/80"
|
||||||
className="w-full rounded-xl border border-neutral-200/70 bg-white/70 px-3 py-2 text-sm shadow-sm outline-none"
|
|
||||||
>
|
>
|
||||||
<option value="all">ทุกช่องทาง</option>
|
ปรับแต่งตาราง
|
||||||
<option value="shopee">Shopee</option>
|
</button>
|
||||||
<option value="lazada">Lazada</option>
|
<button
|
||||||
<option value="tiktok">TikTok</option>
|
onClick={() => downloadCsv("orders_visible.csv", rows, visibleCols)}
|
||||||
<option value="d2c">เว็บไซต์ (D2C)</option>
|
className="rounded-xl border border-neutral-200/70 bg-white/70 px-3 py-1.5 text-sm shadow-sm hover:bg-neutral-100/80"
|
||||||
<option value="chat">แชต</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div className="sm:col-span-2">
|
|
||||||
<select
|
|
||||||
value={payment}
|
|
||||||
onChange={(e) => setPayment(e.target.value as "all" | PaymentStatus)}
|
|
||||||
className="w-full rounded-xl border border-neutral-200/70 bg-white/70 px-3 py-2 text-sm shadow-sm outline-none"
|
|
||||||
>
|
>
|
||||||
<option value="all">การชำระเงิน: ทั้งหมด</option>
|
ส่งออก CSV (เฉพาะคอลัมน์ที่แสดง)
|
||||||
<option value="paid">ชำระแล้ว (paid)</option>
|
</button>
|
||||||
<option value="pending">รอชำระ (pending)</option>
|
<button className="rounded-xl bg-neutral-900 px-3 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-neutral-800">
|
||||||
<option value="failed">ล้มเหลว (failed)</option>
|
สร้างออเดอร์
|
||||||
<option value="cod">COD</option>
|
</button>
|
||||||
<option value="refunded">คืนเงิน (refunded)</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div className="sm:col-span-2">
|
|
||||||
<select
|
|
||||||
value={fulfillment}
|
|
||||||
onChange={(e) => setFulfillment(e.target.value as "all" | FulfillmentStatus)}
|
|
||||||
className="w-full rounded-xl border border-neutral-200/70 bg-white/70 px-3 py-2 text-sm shadow-sm outline-none"
|
|
||||||
>
|
|
||||||
<option value="all">จัดส่ง: ทั้งหมด</option>
|
|
||||||
<option value="unfulfilled">ยังไม่จัดส่ง (unfulfilled)</option>
|
|
||||||
<option value="processing">กำลังดำเนินการ (processing)</option>
|
|
||||||
<option value="shipped">ส่งแล้ว (shipped)</option>
|
|
||||||
<option value="delivered">ถึงปลายทาง (delivered)</option>
|
|
||||||
<option value="returned">ตีกลับ (returned)</option>
|
|
||||||
<option value="cancelled">ยกเลิก (cancelled)</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
|
||||||
|
|
||||||
{/* Table — ดึงสกรอลบาร์ออกนอก padding ด้วย -mx-5 แล้วชดเชย px-5 */}
|
{/* Filters */}
|
||||||
<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="-mx-5 overflow-x-auto">
|
<div className="grid gap-3 sm:grid-cols-12">
|
||||||
<div className="px-5">
|
<div className="sm:col-span-6">
|
||||||
<table className="min-w-full table-auto text-sm">
|
<input
|
||||||
<thead>
|
placeholder="ค้นหา: เลขออเดอร์ / อ้างอิง / ลูกค้า / เบอร์ / อีเมล / ผู้เปิด / เลขภาษี / Tracking…"
|
||||||
<tr className="text-neutral-600">
|
value={q}
|
||||||
{topHeaderSegments.map((seg, idx) => (
|
onChange={(e) => setQ(e.target.value)}
|
||||||
<th
|
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"
|
||||||
key={`${seg.groupTH}-${idx}`}
|
/>
|
||||||
colSpan={seg.colSpan}
|
</div>
|
||||||
className="pb-1 pr-4 whitespace-nowrap text-center text-[12px] font-semibold"
|
<div className="sm:col-span-2">
|
||||||
>
|
<select
|
||||||
{seg.groupTH}
|
value={channel}
|
||||||
</th>
|
onChange={(e) => setChannel(e.target.value as "all" | Channel)}
|
||||||
))}
|
className="w-full rounded-xl border border-neutral-200/70 bg-white/70 px-3 py-2 text-sm shadow-sm outline-none"
|
||||||
</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={`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>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{openCustomize && (
|
|
||||||
<div
|
|
||||||
className="fixed inset-0 z-40 flex items-end justify-center bg-black/40 p-3 sm:items-center"
|
|
||||||
onClick={() => setOpenCustomize(false)}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
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()}
|
|
||||||
>
|
|
||||||
<div className="flex shrink-0 items-center justify-between border-b border-neutral-200/70 px-5 py-4">
|
|
||||||
<h3 className="text-lg font-semibold tracking-tight">ปรับแต่งตาราง</h3>
|
|
||||||
<button
|
|
||||||
onClick={() => setOpenCustomize(false)}
|
|
||||||
className="rounded-lg border border-neutral-200/70 bg-white/70 px-2.5 py-1.5 text-sm shadow-sm hover:bg-neutral-100/80"
|
|
||||||
>
|
>
|
||||||
ปิด
|
<option value="all">ทุกช่องทาง</option>
|
||||||
</button>
|
<option value="shopee">Shopee</option>
|
||||||
|
<option value="lazada">Lazada</option>
|
||||||
|
<option value="tiktok">TikTok</option>
|
||||||
|
<option value="d2c">เว็บไซต์ (D2C)</option>
|
||||||
|
<option value="chat">แชต</option>
|
||||||
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="sm:col-span-2">
|
||||||
|
<select
|
||||||
|
value={payment}
|
||||||
|
onChange={(e) => setPayment(e.target.value as "all" | PaymentStatus)}
|
||||||
|
className="w-full rounded-xl border border-neutral-200/70 bg-white/70 px-3 py-2 text-sm shadow-sm outline-none"
|
||||||
|
>
|
||||||
|
<option value="all">การชำระเงิน: ทั้งหมด</option>
|
||||||
|
<option value="paid">ชำระแล้ว (paid)</option>
|
||||||
|
<option value="pending">รอชำระ (pending)</option>
|
||||||
|
<option value="failed">ล้มเหลว (failed)</option>
|
||||||
|
<option value="cod">COD</option>
|
||||||
|
<option value="refunded">คืนเงิน (refunded)</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div className="sm:col-span-2">
|
||||||
|
<select
|
||||||
|
value={fulfillment}
|
||||||
|
onChange={(e) => setFulfillment(e.target.value as "all" | FulfillmentStatus)}
|
||||||
|
className="w-full rounded-xl border border-neutral-200/70 bg-white/70 px-3 py-2 text-sm shadow-sm outline-none"
|
||||||
|
>
|
||||||
|
<option value="all">จัดส่ง: ทั้งหมด</option>
|
||||||
|
<option value="unfulfilled">ยังไม่จัดส่ง (unfulfilled)</option>
|
||||||
|
<option value="processing">กำลังดำเนินการ (processing)</option>
|
||||||
|
<option value="shipped">ส่งแล้ว (shipped)</option>
|
||||||
|
<option value="delivered">ถึงปลายทาง (delivered)</option>
|
||||||
|
<option value="returned">ตีกลับ (returned)</option>
|
||||||
|
<option value="cancelled">ยกเลิก (cancelled)</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<div className="grow overflow-y-auto p-5">
|
{/* Table — เก็บเนื้อหาทั้งหมดด้วยสกรอลล์แนวนอน และสกรอลล์บาร์ชิดขอบการ์ด */}
|
||||||
<div className="mb-4 flex flex-wrap items-center gap-2">
|
<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)]">
|
||||||
<button
|
<div className="-mx-5 overflow-x-auto">
|
||||||
onClick={() => setTempKeys(ALL_COLS.map((c) => c.key))}
|
<div className="px-5">
|
||||||
className="rounded-xl border border-neutral-200/70 bg-white/70 px-3 py-1.5 text-xs shadow-sm hover:bg-neutral-100/80"
|
<table className="min-w-full table-auto text-sm">
|
||||||
>
|
<thead>
|
||||||
เลือกทั้งหมด
|
<tr className="text-neutral-600">
|
||||||
</button>
|
{topHeaderSegments.map((seg, idx) => (
|
||||||
<button
|
<th
|
||||||
onClick={() => setTempKeys([])}
|
key={`${seg.groupTH}-${idx}`}
|
||||||
className="rounded-xl border border-neutral-200/70 bg-white/70 px-3 py-1.5 text-xs shadow-sm hover:bg-neutral-100/80"
|
colSpan={seg.colSpan}
|
||||||
>
|
className="whitespace-nowrap pb-1 pr-4 text-center text-[12px] font-semibold"
|
||||||
ล้างการเลือก
|
>
|
||||||
</button>
|
{seg.groupTH}
|
||||||
<button
|
</th>
|
||||||
onClick={() => setTempKeys(DEFAULT_VISIBLE)}
|
))}
|
||||||
className="rounded-xl border border-neutral-200/70 bg-white/70 px-3 py-1.5 text-xs shadow-sm hover:bg-neutral-100/80"
|
</tr>
|
||||||
>
|
<tr className="text-left text-neutral-500">
|
||||||
คืนค่าเริ่มต้น
|
{visibleCols.map((c) => (
|
||||||
</button>
|
<th
|
||||||
<span className="text-xs text-neutral-500">เลือก {tempKeys.length} คอลัมน์</span>
|
key={String(c.key)}
|
||||||
</div>
|
className={`whitespace-nowrap pb-2 pr-4 ${c.align === "right" ? "text-right" : ""}`}
|
||||||
|
>
|
||||||
|
{c.labelTH}
|
||||||
|
</th>
|
||||||
|
))}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
<div className="grid gap-4 sm:grid-cols-2">
|
<tbody className="divide-y divide-neutral-200/70">
|
||||||
{GROUPS.map((g) => (
|
{rows.map((o) => (
|
||||||
<fieldset key={g.titleTH} className="rounded-2xl border border-neutral-200/70 bg-white/60 p-4">
|
<tr key={o.id} className="align-middle">
|
||||||
<div className="mb-2 flex items-center justify-between">
|
{visibleCols.map((c) => {
|
||||||
<legend className="text-sm font-semibold">{g.titleTH}</legend>
|
const raw = getProp(o, c.key);
|
||||||
<div className="flex gap-1">
|
const content: ReactNode = c.format ? c.format(raw, o) : defaultCell(raw);
|
||||||
<button
|
const align = c.align === "right" ? "text-right" : "";
|
||||||
className="rounded-lg border border-neutral-200/70 bg-white/70 px-2 py-1 text-[11px] shadow-sm hover:bg-neutral-100/80"
|
return (
|
||||||
onClick={() => setTempKeys((prev) => Array.from(new Set([...prev, ...g.keys])))}
|
<td key={`${o.id}-${String(c.key)}`} className={`whitespace-nowrap py-2 pr-4 ${align}`}>
|
||||||
>
|
{c.key === "orderNo" ? (
|
||||||
เลือกกลุ่มนี้
|
<a
|
||||||
</button>
|
href={`/orders/${o.id}`}
|
||||||
<button
|
className="text-neutral-900 underline decoration-neutral-200 underline-offset-4 hover:decoration-neutral-400"
|
||||||
className="rounded-lg border border-neutral-200/70 bg-white/70 px-2 py-1 text-[11px] shadow-sm hover:bg-neutral-100/80"
|
>
|
||||||
onClick={() => setTempKeys((prev) => prev.filter((k) => !g.keys.includes(k)))}
|
{content}
|
||||||
>
|
</a>
|
||||||
เอาออกกลุ่มนี้
|
) : c.key === "tags" && Array.isArray(o.tags) ? (
|
||||||
</button>
|
<div className="flex gap-1">
|
||||||
</div>
|
{o.tags!.length ? (
|
||||||
</div>
|
o.tags!.map((t) => (
|
||||||
<div className="grid grid-cols-1 gap-2 sm:grid-cols-2">
|
<span
|
||||||
{g.keys.map((k) => {
|
key={t}
|
||||||
const col = ALL_COLS.find((c) => c.key === k)!;
|
className="rounded-full border border-neutral-200 bg-neutral-50 px-2 py-0.5 text-[11px] text-neutral-700"
|
||||||
const checked = tempKeys.includes(k);
|
>
|
||||||
return (
|
{t}
|
||||||
<label key={k} className="flex cursor-pointer items-center gap-2 text-sm">
|
</span>
|
||||||
<input
|
))
|
||||||
type="checkbox"
|
) : (
|
||||||
className="h-4 w-4 rounded border-neutral-300 text-neutral-900 focus:ring-neutral-900"
|
<span>-</span>
|
||||||
checked={checked}
|
)}
|
||||||
onChange={(e) =>
|
</div>
|
||||||
setTempKeys((prev) =>
|
) : (
|
||||||
e.target.checked ? [...prev, k] : prev.filter((x) => x !== k),
|
content
|
||||||
)
|
)}
|
||||||
}
|
</td>
|
||||||
/>
|
);
|
||||||
<span>{col.labelTH}</span>
|
})}
|
||||||
</label>
|
</tr>
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</fieldset>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<div className="flex shrink-0 items-center justify-between border-t border-neutral-200/70 px-5 py-4">
|
{openCustomize && (
|
||||||
<div className="text-xs text-neutral-500">
|
<div
|
||||||
เคล็ดลับ: ระบบจะจำคอลัมน์ที่คุณเลือกไว้ในเบราว์เซอร์นี้
|
className="fixed inset-0 z-40 flex items-end justify-center bg-black/40 p-3 sm:items-center"
|
||||||
</div>
|
onClick={() => setOpenCustomize(false)}
|
||||||
<div className="flex items-center gap-2">
|
>
|
||||||
|
<div
|
||||||
|
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()}
|
||||||
|
>
|
||||||
|
<div className="flex shrink-0 items-center justify-between border-b border-neutral-200/70 px-5 py-4">
|
||||||
|
<h3 className="text-lg font-semibold tracking-tight">ปรับแต่งตาราง</h3>
|
||||||
<button
|
<button
|
||||||
onClick={() => setOpenCustomize(false)}
|
onClick={() => setOpenCustomize(false)}
|
||||||
className="rounded-xl border border-neutral-200/70 bg-white/70 px-3 py-1.5 text-sm shadow-sm hover:bg-neutral-100/80"
|
className="rounded-lg border border-neutral-200/70 bg-white/70 px-2.5 py-1.5 text-sm shadow-sm hover:bg-neutral-100/80"
|
||||||
>
|
>
|
||||||
ยกเลิก
|
ปิด
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
disabled={tempKeys.length === 0}
|
|
||||||
onClick={() => {
|
|
||||||
persistVisible(tempKeys);
|
|
||||||
setOpenCustomize(false);
|
|
||||||
}}
|
|
||||||
className={`rounded-xl px-3 py-1.5 text-sm font-semibold shadow-sm ${
|
|
||||||
tempKeys.length === 0 ? "cursor-not-allowed bg-neutral-200 text-neutral-500" : "bg-neutral-900 text-white hover:bg-neutral-800"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
ใช้ค่าที่เลือก
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="grow overflow-y-auto p-5">
|
||||||
|
<div className="mb-4 flex flex-wrap items-center gap-2">
|
||||||
|
<button
|
||||||
|
onClick={() => setTempKeys(ALL_COLS.map((c) => c.key))}
|
||||||
|
className="rounded-xl border border-neutral-200/70 bg-white/70 px-3 py-1.5 text-xs shadow-sm hover:bg-neutral-100/80"
|
||||||
|
>
|
||||||
|
เลือกทั้งหมด
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setTempKeys([])}
|
||||||
|
className="rounded-xl border border-neutral-200/70 bg-white/70 px-3 py-1.5 text-xs shadow-sm hover:bg-neutral-100/80"
|
||||||
|
>
|
||||||
|
ล้างการเลือก
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setTempKeys(DEFAULT_VISIBLE)}
|
||||||
|
className="rounded-xl border border-neutral-200/70 bg-white/70 px-3 py-1.5 text-xs shadow-sm hover:bg-neutral-100/80"
|
||||||
|
>
|
||||||
|
คืนค่าเริ่มต้น
|
||||||
|
</button>
|
||||||
|
<span className="text-xs text-neutral-500">เลือก {tempKeys.length} คอลัมน์</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid gap-4 sm:grid-cols-2">
|
||||||
|
{GROUPS.map((g) => (
|
||||||
|
<fieldset key={g.titleTH} className="rounded-2xl border border-neutral-200/70 bg-white/60 p-4">
|
||||||
|
<div className="mb-2 flex items-center justify-between">
|
||||||
|
<legend className="text-sm font-semibold">{g.titleTH}</legend>
|
||||||
|
<div className="flex gap-1">
|
||||||
|
<button
|
||||||
|
className="rounded-lg border border-neutral-200/70 bg-white/70 px-2 py-1 text-[11px] shadow-sm hover:bg-neutral-100/80"
|
||||||
|
onClick={() => setTempKeys((prev) => Array.from(new Set([...prev, ...g.keys])))}
|
||||||
|
>
|
||||||
|
เลือกกลุ่มนี้
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="rounded-lg border border-neutral-200/70 bg-white/70 px-2 py-1 text-[11px] shadow-sm hover:bg-neutral-100/80"
|
||||||
|
onClick={() => setTempKeys((prev) => prev.filter((k) => !g.keys.includes(k)))}
|
||||||
|
>
|
||||||
|
เอาออกกลุ่มนี้
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-1 gap-2 sm:grid-cols-2">
|
||||||
|
{g.keys.map((k) => {
|
||||||
|
const col = ALL_COLS.find((c) => c.key === k)!;
|
||||||
|
const checked = tempKeys.includes(k);
|
||||||
|
return (
|
||||||
|
<label key={k} className="flex cursor-pointer items-center gap-2 text-sm">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className="h-4 w-4 rounded border-neutral-300 text-neutral-900 focus:ring-neutral-900"
|
||||||
|
checked={checked}
|
||||||
|
onChange={(e) =>
|
||||||
|
setTempKeys((prev) =>
|
||||||
|
e.target.checked ? [...prev, k] : prev.filter((x) => x !== k),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<span>{col.labelTH}</span>
|
||||||
|
</label>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex shrink-0 items-center justify-between border-t border-neutral-200/70 px-5 py-4">
|
||||||
|
<div className="text-xs text-neutral-500">
|
||||||
|
เคล็ดลับ: ระบบจะจำคอลัมน์ที่คุณเลือกไว้ในเบราว์เซอร์นี้
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<button
|
||||||
|
onClick={() => setOpenCustomize(false)}
|
||||||
|
className="rounded-xl border border-neutral-200/70 bg-white/70 px-3 py-1.5 text-sm shadow-sm hover:bg-neutral-100/80"
|
||||||
|
>
|
||||||
|
ยกเลิก
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
disabled={tempKeys.length === 0}
|
||||||
|
onClick={() => {
|
||||||
|
persistVisible(tempKeys);
|
||||||
|
setOpenCustomize(false);
|
||||||
|
}}
|
||||||
|
className={`rounded-xl px-3 py-1.5 text-sm font-semibold shadow-sm ${
|
||||||
|
tempKeys.length === 0
|
||||||
|
? "cursor-not-allowed bg-neutral-200 text-neutral-500"
|
||||||
|
: "bg-neutral-900 text-white hover:bg-neutral-800"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
ใช้ค่าที่เลือก
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
)}
|
</div>
|
||||||
</div>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
|
|
||||||
|
html { scrollbar-gutter: stable both-edges; }
|
||||||
|
|
||||||
|
@supports not (scrollbar-gutter: stable both-edges) {
|
||||||
|
html { overflow-y: scroll; }
|
||||||
|
}
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--background: #ffffff;
|
--background: #ffffff;
|
||||||
--foreground: #171717;
|
--foreground: #171717;
|
||||||
|
|||||||
Reference in New Issue
Block a user