feat: add responsive card layout for tables on mobile screens

Tables show as cards on small screens (<640px) with key rate fields
in a compact grid, while desktop retains the full table view.
This commit is contained in:
tiennm99 committed 2026-04-04 13:28:01 +07:00
1 parent e235aaff6d
commit 8bd463227b
1 file changed
+135 -78
+135 -78
View File
@@ -13,49 +13,69 @@ export function ComparisonTable({ results }) {
];
return (
<div className="overflow-x-auto rounded-lg border border-[var(--card-border)]">
<table className="min-w-full text-sm">
<thead className="sticky top-0 z-10">
<tr className="bg-[var(--table-header-bg)]">
{headers.map((h) => (
<th
key={h.label}
className={`px-3 py-2.5 font-semibold whitespace-nowrap border-b border-[var(--card-border)] ${h.numeric ? "text-right" : "text-left"}`}
>
{h.label}
</th>
))}
</tr>
</thead>
<tbody>
{results.map((row, idx) => {
const cells = [
{ value: row.date, numeric: false },
{ value: row.currency, numeric: false },
{ value: row.bidvBuyTm, numeric: true },
{ value: row.bidvSell, numeric: true },
{ value: row.tcbBidTm, numeric: true },
{ value: row.tcbAsk, numeric: true },
];
return (
<tr
key={idx}
className={`transition-colors hover:!bg-[var(--table-row-hover)] ${idx % 2 !== 0 ? "bg-[var(--table-row-stripe)]" : ""}`}
>
{cells.map((cell, i) => (
<td
key={i}
className={`px-3 py-2 whitespace-nowrap border-b border-[var(--card-border)] ${cell.numeric ? "text-right tabular-nums" : ""}`}
>
{cell.value || "-"}
</td>
))}
</tr>
);
})}
</tbody>
</table>
</div>
<>
{/* Desktop table */}
<div className="hidden sm:block overflow-x-auto rounded-lg border border-[var(--card-border)]">
<table className="min-w-full text-sm">
<thead className="sticky top-0 z-10">
<tr className="bg-[var(--table-header-bg)]">
{headers.map((h) => (
<th
key={h.label}
className={`px-3 py-2.5 font-semibold whitespace-nowrap border-b border-[var(--card-border)] ${h.numeric ? "text-right" : "text-left"}`}
>
{h.label}
</th>
))}
</tr>
</thead>
<tbody>
{results.map((row, idx) => {
const cells = [
{ value: row.date, numeric: false },
{ value: row.currency, numeric: false },
{ value: row.bidvBuyTm, numeric: true },
{ value: row.bidvSell, numeric: true },
{ value: row.tcbBidTm, numeric: true },
{ value: row.tcbAsk, numeric: true },
];
return (
<tr
key={idx}
className={`transition-colors hover:!bg-[var(--table-row-hover)] ${idx % 2 !== 0 ? "bg-[var(--table-row-stripe)]" : ""}`}
>
{cells.map((cell, i) => (
<td
key={i}
className={`px-3 py-2 whitespace-nowrap border-b border-[var(--card-border)] ${cell.numeric ? "text-right tabular-nums" : ""}`}
>
{cell.value || "-"}
</td>
))}
</tr>
);
})}
</tbody>
</table>
</div>
{/* Mobile cards */}
<div className="sm:hidden flex flex-col gap-2">
{results.map((row, idx) => (
<div key={idx} className="rounded-lg border border-[var(--card-border)] p-3 bg-[var(--card-bg)] text-sm">
<div className="flex justify-between mb-1.5">
<span className="font-medium">{row.date}</span>
<span className="text-[var(--muted)]">{row.currency}</span>
</div>
<div className="grid grid-cols-2 gap-x-4 gap-y-1 text-xs">
<div className="flex justify-between"><span className="text-[var(--muted)]">BIDV Buy</span><span className="tabular-nums">{row.bidvBuyTm || "-"}</span></div>
<div className="flex justify-between"><span className="text-[var(--muted)]">TCB Bid</span><span className="tabular-nums">{row.tcbBidTm || "-"}</span></div>
<div className="flex justify-between"><span className="text-[var(--muted)]">BIDV Sell</span><span className="tabular-nums">{row.bidvSell || "-"}</span></div>
<div className="flex justify-between"><span className="text-[var(--muted)]">TCB Ask</span><span className="tabular-nums">{row.tcbAsk || "-"}</span></div>
</div>
</div>
))}
</div>
</>
);
}
@@ -106,42 +126,79 @@ export function RateTable({ results, selectedBank }) {
];
};
const getMobileCard = (row) => {
if (selectedBank === "bidv") {
return { title: row.nameVI || row.nameEN, subtitle: `${row.currency} - ${row.date}`, fields: [
{ label: "Buy TM", value: row.muaTm }, { label: "Buy CK", value: row.muaCk }, { label: "Sell", value: row.ban },
]};
}
return { title: row.label, subtitle: `${row.sourceCurrency} - ${row.date}`, fields: [
{ label: "Bid TM", value: row.bidRateTM }, { label: "Bid CK", value: row.bidRateCK },
{ label: "Ask", value: row.askRate }, { label: "Ask TM", value: row.askRateTM },
]};
};
return (
<div className="overflow-x-auto rounded-lg border border-[var(--card-border)]">
<table className="min-w-full text-sm">
<thead className="sticky top-0 z-10">
<tr className="bg-[var(--table-header-bg)]">
{headers.map((h) => (
<th
key={h.label}
className={`px-3 py-2.5 font-semibold whitespace-nowrap border-b border-[var(--card-border)] ${h.numeric ? "text-right" : "text-left"}`}
>
{h.label}
</th>
))}
</tr>
</thead>
<tbody>
{results.map((row, idx) => {
const cells = getRowCells(row);
return (
<tr
key={idx}
className={`transition-colors hover:!bg-[var(--table-row-hover)] ${idx % 2 !== 0 ? "bg-[var(--table-row-stripe)]" : ""}`}
>
{cells.map((cell, i) => (
<td
key={i}
className={`px-3 py-2 whitespace-nowrap border-b border-[var(--card-border)] ${cell.numeric ? "text-right tabular-nums" : ""}`}
>
{cell.value}
</td>
<>
{/* Desktop table */}
<div className="hidden sm:block overflow-x-auto rounded-lg border border-[var(--card-border)]">
<table className="min-w-full text-sm">
<thead className="sticky top-0 z-10">
<tr className="bg-[var(--table-header-bg)]">
{headers.map((h) => (
<th
key={h.label}
className={`px-3 py-2.5 font-semibold whitespace-nowrap border-b border-[var(--card-border)] ${h.numeric ? "text-right" : "text-left"}`}
>
{h.label}
</th>
))}
</tr>
</thead>
<tbody>
{results.map((row, idx) => {
const cells = getRowCells(row);
return (
<tr
key={idx}
className={`transition-colors hover:!bg-[var(--table-row-hover)] ${idx % 2 !== 0 ? "bg-[var(--table-row-stripe)]" : ""}`}
>
{cells.map((cell, i) => (
<td
key={i}
className={`px-3 py-2 whitespace-nowrap border-b border-[var(--card-border)] ${cell.numeric ? "text-right tabular-nums" : ""}`}
>
{cell.value}
</td>
))}
</tr>
);
})}
</tbody>
</table>
</div>
{/* Mobile cards */}
<div className="sm:hidden flex flex-col gap-2">
{results.map((row, idx) => {
const card = getMobileCard(row);
return (
<div key={idx} className="rounded-lg border border-[var(--card-border)] p-3 bg-[var(--card-bg)] text-sm">
<div className="flex justify-between mb-1.5">
<span className="font-medium">{card.title}</span>
<span className="text-[var(--muted)] text-xs">{card.subtitle}</span>
</div>
<div className="grid grid-cols-2 gap-x-4 gap-y-1 text-xs">
{card.fields.map((f, i) => (
<div key={i} className="flex justify-between">
<span className="text-[var(--muted)]">{f.label}</span>
<span className="tabular-nums">{f.value || "-"}</span>
</div>
))}
</tr>
);
})}
</tbody>
</table>
</div>
</div>
</div>
);
})}
</div>
</>
);
}