From 669dc3f2a6fe34deaca4950797117606484098ec Mon Sep 17 00:00:00 2001 From: npeter83 Date: Sun, 19 Jul 2026 18:24:25 +0200 Subject: [PATCH] refactor(managers): tidy channel header + column filters + move Actions (E4) - Drop the intro blurb (+ its orphan channels.intro i18n key and Trans import) - Header is now two tight rows: tabs + stats (right), your-tags + sync/backfill (right), leaving room on the tab row for future tabs - Add a funnel filter on the Prio / Channel / Sync / Tags columns (client-side, like the discovery table's Channel filter) - Move the Actions column to 2nd position (after Prio, before Channel), left-aligned --- frontend/src/components/Channels.tsx | 210 +++++++++++---------- frontend/src/i18n/locales/en/channels.json | 1 - frontend/src/i18n/locales/hu/channels.json | 1 - 3 files changed, 110 insertions(+), 102 deletions(-) diff --git a/frontend/src/components/Channels.tsx b/frontend/src/components/Channels.tsx index bd5fdf18..dd2dd86a 100644 --- a/frontend/src/components/Channels.tsx +++ b/frontend/src/components/Channels.tsx @@ -1,5 +1,5 @@ import { useEffect, useRef, useState } from "react"; -import { Trans, useTranslation } from "react-i18next"; +import { useTranslation } from "react-i18next"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { ArrowDown, @@ -248,6 +248,11 @@ export default function Channels() { if (ok) resetBackfill.mutate({ id: c.id, name: c.title ?? c.id }); }; + // Distinct priority levels present in the catalog → the priority column's filter options. + const prioOptions = [...new Set((channelsQuery.data ?? []).map((c) => c.priority))] + .sort((a, b) => b - a) + .map((p) => ({ value: String(p), label: String(p) })); + const columns: Column[] = [ { key: "priority", @@ -257,13 +262,34 @@ export default function Channels() { sortable: true, hideInCard: true, sortValue: (c) => c.priority, + filter: { kind: "select", options: prioOptions, test: (c, v) => String(c.priority) === v }, render: (c) => bumpPriority(c.id, c.priority, d)} />, }, + { + key: "actions", + header: t("channels.cols.actions"), + align: "left", + nowrap: true, + width: "104px", + cardLabel: false, + render: (c) => ( + patch.mutate({ id: c.id, body: { hidden: !c.hidden } })} + onDeep={() => patch.mutate({ id: c.id, body: { deep_requested: !c.deep_requested } })} + onReset={() => onReset(c)} + onUnsubscribe={() => onUnsub(c)} + /> + ), + }, { key: "channel", header: t("channels.cols.channel"), sortable: true, sortValue: (c) => (c.title ?? c.id).toLowerCase(), + filter: { kind: "text", get: (c) => `${c.title ?? ""} ${c.handle ?? ""}` }, cardPrimary: true, render: (c) => ( (v === "fully" ? c.recent_synced && c.backfill_done : v === "needsFull" ? !c.backfill_done : true), + }, render: (c) => , }, { key: "tags", header: t("channels.cols.tags"), cardLabel: false, + filter: { + kind: "multi", + options: userTags.map((tg) => ({ value: String(tg.id), label: tg.name })), + test: (c, values) => values.some((v) => c.tag_ids.includes(Number(v))), + }, render: (c) => ( ), }, - { - key: "actions", - header: t("channels.cols.actions"), - align: "right", - nowrap: true, - width: "104px", - cardLabel: false, - render: (c) => ( - patch.mutate({ id: c.id, body: { hidden: !c.hidden } })} - onDeep={() => patch.mutate({ id: c.id, body: { deep_requested: !c.deep_requested } })} - onReset={() => onReset(c)} - onUnsubscribe={() => onUnsub(c)} - /> - ), - }, ]; // Status chips sit in the table's controls row (next to paging) — compact and close to @@ -375,8 +395,69 @@ export default function Channels() { ); + // The catalog-wide stats ride on the right of the tab row (subscribed only), leaving room for + // future tabs on the left. + const statsBlock = ( +
+ {s && ( + <> + + + + {s.deep_pending_count > 0 && ( + + )} + + + + )} +
+ ); + + // Sync + backfill — now share the tags row (right side). + const actionButtons = ( +
+ + + + + + +
+ ); + const tabs = ( -
+
{(["subscribed", "discovery", "blocked"] as ChannelsView[]).map((v) => (
+ {view === "subscribed" && statsBlock}
); @@ -403,85 +485,11 @@ export default function Channels() { {tabs} {view === "subscribed" && ( -
- {/* Per-user sync status + catalog-wide actions on one row (search/tags filtering - now lives in the table headers). */} -
-
- {s && ( - <> - - - - {s.deep_pending_count > 0 && ( - - )} - - - - )} -
-
- - - - - - -
-
- -

- , - , - , - ]} - /> -

- - {/* The channel-name search now lives in the shared top SearchBar (driven via props). */} - - {/* Your tags — click a chip to filter the table by it (add/rename/delete live in the manager). */} -
+
+ {/* Your tags (left) share one row with the two catalog-wide actions (right); the channel-name + search lives in the shared top SearchBar, tag create/delete in the manager. */} +
+
{t("channels.tags.yourTags")} @@ -522,6 +530,8 @@ export default function Channels() { {t("channels.tags.manage")}
+ {actionButtons} +
)} diff --git a/frontend/src/i18n/locales/en/channels.json b/frontend/src/i18n/locales/en/channels.json index 7e474b90..13e0082e 100644 --- a/frontend/src/i18n/locales/en/channels.json +++ b/frontend/src/i18n/locales/en/channels.json @@ -1,5 +1,4 @@ { - "intro": "Set a channel's <0>priority to push its videos up when you sort by “Channel priority”, attach your own <1>tags to filter the feed, or <2>hide a channel to drop it from the feed without unsubscribing.", "syncSubscriptions": "Read subscriptions from YouTube", "syncSubscriptionsHint": "Re-import your subscription list from YouTube — adds channels you've newly followed and drops ones you've unfollowed. The videos themselves keep syncing automatically in the background; this does not re-fetch them.", "backfillEverything": "Backfill everything", diff --git a/frontend/src/i18n/locales/hu/channels.json b/frontend/src/i18n/locales/hu/channels.json index c0b4f68f..c9425b57 100644 --- a/frontend/src/i18n/locales/hu/channels.json +++ b/frontend/src/i18n/locales/hu/channels.json @@ -1,5 +1,4 @@ { - "intro": "Állíts be egy csatornának <0>prioritást, hogy a videói előrébb kerüljenek, amikor „Csatorna prioritás” szerint rendezel, csatolj saját <1>címkéket a hírfolyam szűréséhez, vagy <2>rejts el egy csatornát, hogy leiratkozás nélkül kivedd a hírfolyamból.", "syncSubscriptions": "Feliratkozások beolvasása a YouTube-ról", "syncSubscriptionsHint": "Újraimportálja a feliratkozási listádat a YouTube-ról — hozzáadja az újonnan követett csatornákat, és eltávolítja azokat, amelyekről leiratkoztál. Maguk a videók a háttérben automatikusan tovább szinkronizálódnak; ez nem tölti le őket újra.", "backfillEverything": "Minden letöltése",