Redesign Table

This commit is contained in:
Thanakarn Klangkasame
2025-10-09 14:59:20 +07:00
parent 6821f585d6
commit bdb7e49003

View File

@@ -125,6 +125,9 @@ const pill = (t: Tone) =>
const U = (s: string) => s.toUpperCase(); const U = (s: string) => s.toUpperCase();
// เส้นคอลัมน์โทนอ่อน (ใช้กับทั้ง th/td)
const COL_BORDER = "border-r border-neutral-200/60 last:border-r-0";
const IconLabel = ({ Icon, label, pillTone }: { Icon: LucideIcon; label: string; pillTone?: Tone }) => { const IconLabel = ({ Icon, label, pillTone }: { Icon: LucideIcon; label: string; pillTone?: Tone }) => {
const content = ( const content = (
<span className="inline-flex items-center gap-1.5"> <span className="inline-flex items-center gap-1.5">
@@ -226,7 +229,7 @@ const renderWarehouse = (w: Order["warehouseCode"]) => {
return <IconLabel Icon={Icon} label={label} />; return <IconLabel Icon={Icon} label={label} />;
}; };
/* For selects (native <option> เรนเดอร์ React icon ไม่ได้ จึงใช้ตัวอักษรอย่างเดียว) */ /* For selects */
const CHANNELS = Object.keys(CHANNEL_META) as Channel[]; const CHANNELS = Object.keys(CHANNEL_META) as Channel[];
const PAYMENT_STATUSES = Object.keys(PAYMENT_STATUS_META) as PaymentStatus[]; const PAYMENT_STATUSES = Object.keys(PAYMENT_STATUS_META) as PaymentStatus[];
const FULFILL_STATUSES = Object.keys(FULFILL_META) as FulfillmentStatus[]; const FULFILL_STATUSES = Object.keys(FULFILL_META) as FulfillmentStatus[];
@@ -787,7 +790,7 @@ export default function OrdersPage() {
return ( return (
<> <>
{/* ทำให้กว้างหน้า "นิ่งเท่ากัน" เสมอ โดยจองที่สกรอลล์บาร์ */} {/* กว้างหน้า นิ่ง” + ปรับสไตล์เส้นหัวตาราง */}
<style jsx global>{` <style jsx global>{`
html { scrollbar-gutter: stable both-edges; } html { scrollbar-gutter: stable both-edges; }
@supports not (scrollbar-gutter: stable both-edges) { html { overflow-y: scroll; } } @supports not (scrollbar-gutter: stable both-edges) { html { overflow-y: scroll; } }
@@ -829,7 +832,7 @@ export default function OrdersPage() {
placeholder="ค้นหา: เลขออเดอร์ / อ้างอิง / ลูกค้า / เบอร์ / อีเมล / ผู้เปิด / เลขภาษี / Tracking…" placeholder="ค้นหา: เลขออเดอร์ / อ้างอิง / ลูกค้า / เบอร์ / อีเมล / ผู้เปิด / เลขภาษี / Tracking…"
value={q} value={q}
onChange={(e) => setQ(e.target.value)} 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" className="w-full rounded-xl border border-neutral-200/70 bg-white/70 px-3 py-2.5 text-sm shadow-sm outline-none placeholder:text-neutral-400 focus:border-neutral-300"
/> />
</div> </div>
@@ -837,7 +840,7 @@ export default function OrdersPage() {
<select <select
value={channel} value={channel}
onChange={(e) => setChannel(e.target.value as "all" | 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" className="w-full rounded-xl border border-neutral-200/70 bg-white/70 px-3 py-2.5 text-sm shadow-sm outline-none"
> >
<option value="all"></option> <option value="all"></option>
{CHANNELS.map((c) => ( {CHANNELS.map((c) => (
@@ -852,7 +855,7 @@ export default function OrdersPage() {
<select <select
value={payment} value={payment}
onChange={(e) => setPayment(e.target.value as "all" | PaymentStatus)} 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" className="w-full rounded-xl border border-neutral-200/70 bg-white/70 px-3 py-2.5 text-sm shadow-sm outline-none"
> >
<option value="all">การชำระเงิน: ทั้งหมด</option> <option value="all">การชำระเงิน: ทั้งหมด</option>
{PAYMENT_STATUSES.map((ps) => ( {PAYMENT_STATUSES.map((ps) => (
@@ -867,7 +870,7 @@ export default function OrdersPage() {
<select <select
value={fulfillment} value={fulfillment}
onChange={(e) => setFulfillment(e.target.value as "all" | FulfillmentStatus)} 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" className="w-full rounded-xl border border-neutral-200/70 bg-white/70 px-3 py-2.5 text-sm shadow-sm outline-none"
> >
<option value="all">จัดส่ง: ทั้งหมด</option> <option value="all">จัดส่ง: ทั้งหมด</option>
{FULFILL_STATUSES.map((fs) => ( {FULFILL_STATUSES.map((fs) => (
@@ -884,24 +887,27 @@ export default function OrdersPage() {
<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="-mx-5 overflow-x-auto">
<div className="px-5"> <div className="px-5">
<table className="min-w-full table-auto text-sm"> {/* ใช้ border-separate + border-spacing-0 เพื่อควบคุมเส้นคอลัมน์เอง */}
<table className="min-w-full table-auto border-separate border-spacing-0 text-sm">
<thead> <thead>
<tr className="text-neutral-600"> {/* Group row (กว้างขึ้น + เส้นล่าง + เส้นคอลัมน์อ่อน) */}
<tr className="bg-neutral-50/80 text-neutral-700">
{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="whitespace-nowrap pb-1 pr-4 text-center text-[12px] font-semibold" className={`whitespace-nowrap px-3 py-2.5 text-center text-[13px] font-semibold border-b border-neutral-200/70 ${COL_BORDER}`}
> >
{seg.groupTH} {seg.groupTH}
</th> </th>
))} ))}
</tr> </tr>
<tr className="text-left text-neutral-500"> {/* Column labels (กว้างขึ้น + ตัวหนา + เส้นคอลัมน์อ่อน) */}
<tr className="bg-neutral-50/80 text-neutral-600">
{visibleCols.map((c) => ( {visibleCols.map((c) => (
<th <th
key={String(c.key)} key={String(c.key)}
className={`whitespace-nowrap pb-2 pr-4 ${c.align === "right" ? "text-right" : ""}`} className={`whitespace-nowrap px-3 py-2.5 text-[12.5px] font-semibold border-b border-neutral-200/70 ${c.align === "right" ? "text-right" : "text-left"} ${COL_BORDER}`}
> >
{c.labelTH} {c.labelTH}
</th> </th>
@@ -909,15 +915,19 @@ export default function OrdersPage() {
</tr> </tr>
</thead> </thead>
<tbody className="divide-y divide-neutral-200/70"> {/* แถบเส้นแบ่งแถว + zebra อ่อน ๆ เพื่อ “แบ่งสายตา” */}
{rows.map((o) => ( <tbody className="divide-y divide-neutral-200/60">
<tr key={o.id} className="align-middle"> {rows.map((o, rowIdx) => (
<tr key={o.id} className={rowIdx % 2 === 0 ? "bg-white" : "bg-neutral-50/40"}>
{visibleCols.map((c) => { {visibleCols.map((c) => {
const raw = getProp(o, c.key); const raw = getProp(o, c.key);
const content: ReactNode = c.format ? c.format(raw, o) : defaultCell(raw); const content: ReactNode = c.format ? c.format(raw, o) : defaultCell(raw);
const align = c.align === "right" ? "text-right" : ""; const align = c.align === "right" ? "text-right" : "";
return ( return (
<td key={`${o.id}-${String(c.key)}`} className={`whitespace-nowrap py-2 pr-4 ${align}`}> <td
key={`${o.id}-${String(c.key)}`}
className={`whitespace-nowrap px-3 py-2.5 ${align} ${COL_BORDER}`}
>
{c.key === "orderNo" ? ( {c.key === "orderNo" ? (
<a <a
href={`/orders/${o.id}`} href={`/orders/${o.id}`}