From 83b60df112d7ac22dd6a3659c0cf7dcaf0b57381 Mon Sep 17 00:00:00 2001 From: npeter83 Date: Sun, 19 Jul 2026 15:45:09 +0200 Subject: [PATCH] feat(managers): bottom pager on the channel tables (E4 S1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DataTable gains a working controlsPosition="both": the leading controls (e.g. the status chips) stay ONLY at the top; the bottom row is pager-only (no duplicated chips). The subscribed + discovery channel tables opt in, so the 33-page list has a pager at the bottom too — no scroll back up. --- frontend/src/components/ChannelDiscovery.tsx | 2 +- frontend/src/components/Channels.tsx | 2 +- frontend/src/components/DataTable.tsx | 138 ++++++++++--------- 3 files changed, 78 insertions(+), 64 deletions(-) diff --git a/frontend/src/components/ChannelDiscovery.tsx b/frontend/src/components/ChannelDiscovery.tsx index 6539652..e3b0674 100644 --- a/frontend/src/components/ChannelDiscovery.tsx +++ b/frontend/src/components/ChannelDiscovery.tsx @@ -171,7 +171,7 @@ export default function ChannelDiscovery({ columns={columns} rowKey={(c) => c.id} persistKey={accountKey(LS.channelDiscoveryTable) ?? undefined} - controlsPosition="top" + controlsPosition="both" controlsInBand controlsBandClassName="px-4 max-w-7xl mx-auto" emptyText={t("channels.discovery.empty")} diff --git a/frontend/src/components/Channels.tsx b/frontend/src/components/Channels.tsx index 93a69c4..bd5fdf1 100644 --- a/frontend/src/components/Channels.tsx +++ b/frontend/src/components/Channels.tsx @@ -586,7 +586,7 @@ export default function Channels() { columns={columns} rowKey={(c) => c.id} persistKey={accountKey(LS.channelsTable) ?? undefined} - controlsPosition="top" + controlsPosition="both" controlsLeading={statusChips} controlsInBand controlsBandClassName="px-4 max-w-7xl mx-auto" diff --git a/frontend/src/components/DataTable.tsx b/frontend/src/components/DataTable.tsx index a065a82..e914033 100644 --- a/frontend/src/components/DataTable.tsx +++ b/frontend/src/components/DataTable.tsx @@ -288,70 +288,83 @@ export default function DataTable({ ); } - const controls = - controlsLeading != null || sorted.length > 0 ? ( -
-
{controlsLeading}
-
- {totalPages > 1 && ( -
- - - {t("datatable.pager.pageLabel")} - setPageInput(e.target.value)} - onBlur={commitPageInput} - onKeyDown={(e) => e.key === "Enter" && commitPageInput()} - aria-label={t("datatable.pager.pageLabel")} - className="w-14 bg-card border border-border rounded-md px-1.5 py-1 text-xs text-center tabular-nums outline-none focus:border-accent" - /> - {t("datatable.pager.ofTotal", { total: totalPages })} - - -
- )} - {sorted.length > 0 && ( - - )} -
+ // The pager + rows-per-page cluster on its own, so it can be rendered a second time at the bottom + // (controlsPosition "both") WITHOUT repeating the leading controls (e.g. the status chips) — those + // are header controls and belong only at the top. + const pagerNode = + totalPages > 1 || sorted.length > 0 ? ( +
+ {totalPages > 1 && ( +
+ + + {t("datatable.pager.pageLabel")} + setPageInput(e.target.value)} + onBlur={commitPageInput} + onKeyDown={(e) => e.key === "Enter" && commitPageInput()} + aria-label={t("datatable.pager.pageLabel")} + className="w-14 bg-card border border-border rounded-md px-1.5 py-1 text-xs text-center tabular-nums outline-none focus:border-accent" + /> + {t("datatable.pager.ofTotal", { total: totalPages })} + + +
+ )} + {sorted.length > 0 && ( + + )}
) : null; + const controls = + controlsLeading != null || pagerNode ? ( +
+
{controlsLeading}
+ {pagerNode} +
+ ) : null; + + // Bottom row for "both": just the pager, right-aligned (the leading controls stay at the top). + const bottomControls = pagerNode ? ( +
{pagerNode}
+ ) : null; + const topControls = controlsInBand && controls ? ( @@ -476,7 +489,8 @@ export default function DataTable({
{emptyText ?? t("datatable.empty")}
)} - {(controlsPosition === "bottom" || controlsPosition === "both") && controls} + {controlsPosition === "bottom" && controls} + {controlsPosition === "both" && bottomControls}
); }