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) => ( +
+
+ ))} +
+ + ); +} 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",