Fix Layout
This commit is contained in:
@@ -245,7 +245,7 @@ const MOCK_ORDERS: Order[] = [
|
||||
dimensionsCmW: 12,
|
||||
dimensionsCmH: 8,
|
||||
fulfillStatus: "unfulfilled",
|
||||
shipBy: "2025-10-09T18:00+07:00",
|
||||
shipBy: "2025-10-09T18:00:00+07:00",
|
||||
carrier: "Flash",
|
||||
serviceLevel: "COD-Standard",
|
||||
trackingNo: null,
|
||||
@@ -409,9 +409,7 @@ function downloadCsv(filename: string, rows: Order[], cols: Column[]) {
|
||||
const csv =
|
||||
headers.join(",") +
|
||||
"\n" +
|
||||
rows
|
||||
.map((r) => keys.map((k) => escapeCsv(getProp(r, k))).join(","))
|
||||
.join("\n");
|
||||
rows.map((r) => keys.map((k) => escapeCsv(getProp(r, k))).join(",")).join("\n");
|
||||
const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
@@ -619,281 +617,294 @@ export default function OrdersPage() {
|
||||
}, [visibleCols]);
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Header */}
|
||||
<div className="flex flex-col gap-3 sm:flex-row sm:items-end sm:justify-between">
|
||||
<div>
|
||||
<h1 className="text-3xl font-extrabold tracking-tight">ออเดอร์</h1>
|
||||
<p className="mt-1 text-sm text-neutral-500">
|
||||
มุมมองเต็ม • แสดง {visibleCols.length} ฟิลด์ • ทั้งหมด {rows.length} ออเดอร์
|
||||
</p>
|
||||
</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>
|
||||
<>
|
||||
{/* ทำให้กว้างหน้า "นิ่งเท่ากัน" เสมอ โดยจองที่สกรอลล์บาร์ */}
|
||||
<style jsx global>{`
|
||||
html { scrollbar-gutter: stable both-edges; }
|
||||
@supports not (scrollbar-gutter: stable both-edges) {
|
||||
html { overflow-y: scroll; }
|
||||
}
|
||||
`}</style>
|
||||
|
||||
{/* 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="grid gap-3 sm:grid-cols-12">
|
||||
<div className="sm:col-span-6">
|
||||
<input
|
||||
placeholder="ค้นหา: เลขออเดอร์ / อ้างอิง / ลูกค้า / เบอร์ / อีเมล / ผู้เปิด / เลขภาษี / Tracking…"
|
||||
value={q}
|
||||
onChange={(e) => setQ(e.target.value)}
|
||||
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 className="mx-auto w-full max-w-7xl px-6 space-y-6">
|
||||
{/* Header */}
|
||||
<div className="flex flex-col gap-3 sm:flex-row sm:items-end sm:justify-between">
|
||||
<div>
|
||||
<h1 className="text-3xl font-extrabold tracking-tight">ออเดอร์</h1>
|
||||
<p className="mt-1 text-sm text-neutral-500">
|
||||
มุมมองเต็ม • แสดง {visibleCols.length} ฟิลด์ • ทั้งหมด {rows.length} ออเดอร์
|
||||
</p>
|
||||
</div>
|
||||
<div className="sm:col-span-2">
|
||||
<select
|
||||
value={channel}
|
||||
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"
|
||||
<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"
|
||||
>
|
||||
<option value="all">ทุกช่องทาง</option>
|
||||
<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 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"
|
||||
ปรับแต่งตาราง
|
||||
</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"
|
||||
>
|
||||
<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>
|
||||
ส่งออก 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>
|
||||
</section>
|
||||
|
||||
{/* 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)]">
|
||||
<div className="-mx-5 overflow-x-auto">
|
||||
<div className="px-5">
|
||||
<table className="min-w-full table-auto text-sm">
|
||||
<thead>
|
||||
<tr className="text-neutral-600">
|
||||
{topHeaderSegments.map((seg, idx) => (
|
||||
<th
|
||||
key={`${seg.groupTH}-${idx}`}
|
||||
colSpan={seg.colSpan}
|
||||
className="pb-1 pr-4 whitespace-nowrap text-center text-[12px] font-semibold"
|
||||
>
|
||||
{seg.groupTH}
|
||||
</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={`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"
|
||||
{/* 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)]">
|
||||
<div className="grid gap-3 sm:grid-cols-12">
|
||||
<div className="sm:col-span-6">
|
||||
<input
|
||||
placeholder="ค้นหา: เลขออเดอร์ / อ้างอิง / ลูกค้า / เบอร์ / อีเมล / ผู้เปิด / เลขภาษี / Tracking…"
|
||||
value={q}
|
||||
onChange={(e) => setQ(e.target.value)}
|
||||
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 className="sm:col-span-2">
|
||||
<select
|
||||
value={channel}
|
||||
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"
|
||||
>
|
||||
ปิด
|
||||
</button>
|
||||
<option value="all">ทุกช่องทาง</option>
|
||||
<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 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">
|
||||
<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>
|
||||
{/* Table — เก็บเนื้อหาทั้งหมดด้วยสกรอลล์แนวนอน และสกรอลล์บาร์ชิดขอบการ์ด */}
|
||||
<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="px-5">
|
||||
<table className="min-w-full table-auto text-sm">
|
||||
<thead>
|
||||
<tr className="text-neutral-600">
|
||||
{topHeaderSegments.map((seg, idx) => (
|
||||
<th
|
||||
key={`${seg.groupTH}-${idx}`}
|
||||
colSpan={seg.colSpan}
|
||||
className="whitespace-nowrap pb-1 pr-4 text-center text-[12px] font-semibold"
|
||||
>
|
||||
{seg.groupTH}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
<tr className="text-left text-neutral-500">
|
||||
{visibleCols.map((c) => (
|
||||
<th
|
||||
key={String(c.key)}
|
||||
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">
|
||||
{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>
|
||||
<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>
|
||||
))}
|
||||
</div>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<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">
|
||||
{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-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>
|
||||
</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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
html { scrollbar-gutter: stable both-edges; }
|
||||
|
||||
@supports not (scrollbar-gutter: stable both-edges) {
|
||||
html { overflow-y: scroll; }
|
||||
}
|
||||
|
||||
:root {
|
||||
--background: #ffffff;
|
||||
--foreground: #171717;
|
||||
|
||||
Reference in New Issue
Block a user