diff --git a/frontend/src/components/ChannelStatsPopover.tsx b/frontend/src/components/ChannelStatsPopover.tsx
new file mode 100644
index 00000000..13e7fae9
--- /dev/null
+++ b/frontend/src/components/ChannelStatsPopover.tsx
@@ -0,0 +1,86 @@
+import { useState } from "react";
+import {
+ BarChart3,
+ Clock,
+ Film,
+ History,
+ RefreshCw,
+ Users,
+ Zap,
+ type LucideIcon,
+} from "lucide-react";
+import { useTranslation } from "react-i18next";
+import { type MyStatus } from "../lib/api";
+import { formatEta } from "../lib/format";
+import { PopoverSurface, useAnchoredPopover } from "./AnchoredPopover";
+
+// The channel-manager catalog stats, collapsed from a crowded inline row into a compact "Stats" chip
+// that reveals the numbers on hover (reuses the AnchoredPopover hover mode). One row per stat, each
+// with a leading icon. Kept in the manager's own toolbar (the header's SyncStatus is global; these
+// are channel-catalog figures).
+export default function ChannelStatsPopover({ status }: { status: MyStatus }) {
+ const { t } = useTranslation();
+ const [open, setOpen] = useState(false);
+ const pop = useAnchoredPopover({
+ open,
+ onOpenChange: setOpen,
+ mode: "hover",
+ placement: "bottom-end",
+ offset: 8,
+ });
+
+ const rows: { icon: LucideIcon; label: string; value: string | number }[] = [
+ { icon: Users, label: t("channels.stats.channels"), value: status.channels_total },
+ {
+ icon: RefreshCw,
+ label: t("channels.stats.recentSynced"),
+ value: `${status.channels_recent_synced}/${status.channels_total}`,
+ },
+ {
+ icon: History,
+ label: t("channels.stats.fullHistory"),
+ value: `${status.channels_deep_done}/${status.channels_deep_requested}`,
+ },
+ ...(status.deep_pending_count > 0
+ ? [
+ {
+ icon: Clock,
+ label: t("channels.stats.left"),
+ value: formatEta(status.deep_eta_seconds),
+ },
+ ]
+ : []),
+ { icon: Film, label: t("channels.stats.myVideos"), value: status.my_videos.toLocaleString() },
+ {
+ icon: Zap,
+ label: t("channels.stats.quotaLeft"),
+ value: status.quota_remaining_today.toLocaleString(),
+ },
+ ];
+
+ return (
+ <>
+
+
+ {rows.map((r) => (
+
+
+ {r.label}
+ {r.value}
+
+ ))}
+
+ >
+ );
+}
diff --git a/frontend/src/components/Channels.tsx b/frontend/src/components/Channels.tsx
index a3f8190a..6ca1b90b 100644
--- a/frontend/src/components/Channels.tsx
+++ b/frontend/src/components/Channels.tsx
@@ -7,7 +7,6 @@ import ExcludedChannelList from "./ExcludedChannelList";
import { api, type ManagedChannel } from "../lib/api";
import { notifyYouTubeActionError } from "../lib/youtubeErrors";
import { accountKey, LS } from "../lib/storage";
-import { formatEta } from "../lib/format";
import { buildChannelColumns } from "./channelColumns";
import { notify } from "../lib/notifications";
import Tooltip from "./Tooltip";
@@ -21,6 +20,7 @@ import ViewSwitcher, { type ViewOption } from "./ViewSwitcher";
import ColumnSortControl from "./ColumnSortControl";
import FilterChipGroup, { filterChipClass } from "./FilterChipGroup";
import ActionMenu, { type ActionMenuItem } from "./ActionMenu";
+import ChannelStatsPopover from "./ChannelStatsPopover";
import Pager from "./Pager";
import { CHANNEL_LAYOUTS, type ChannelLayout } from "../lib/channelLayout";
import { sortRows } from "../lib/columnSort";
@@ -305,48 +305,9 @@ 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 && (
-
- )}
-
-
- >
- )}
-
- );
+ // The catalog-wide stats collapse into a compact "Stats" hover chip (right of the tab row, subscribed
+ // only) instead of a crowded inline row.
+ const statsBlock = s ? : null;
// Catalog-wide, occasional actions — tucked into a shared ⋯ overflow menu so they stop crowding the
// tags row. Pending mutations keep the item visible but disabled, with a spinning icon.
@@ -596,13 +557,3 @@ export default function Channels() {
>
);
}
-
-function Stat({ label, value, hint }: { label: string; value: string | number; hint?: string }) {
- return (
-
-
- {value} {label}
-
-
- );
-}
diff --git a/frontend/src/i18n/locales/en/channels.json b/frontend/src/i18n/locales/en/channels.json
index 899ca173..dc942495 100644
--- a/frontend/src/i18n/locales/en/channels.json
+++ b/frontend/src/i18n/locales/en/channels.json
@@ -77,6 +77,7 @@
"actions": "Actions"
},
"stats": {
+ "label": "Stats",
"channels": "Channels",
"channelsHint": "Channels you're subscribed to.",
"recentSynced": "Recent synced",
diff --git a/frontend/src/i18n/locales/hu/channels.json b/frontend/src/i18n/locales/hu/channels.json
index 517ed5b4..a7aec448 100644
--- a/frontend/src/i18n/locales/hu/channels.json
+++ b/frontend/src/i18n/locales/hu/channels.json
@@ -76,6 +76,7 @@
"actions": "Műveletek"
},
"stats": {
+ "label": "Statisztika",
"channels": "Csatorna",
"channelsHint": "Csatornák, amelyekre feliratkoztál.",
"recentSynced": "Legutóbbi szinkronizálva",