refactor(channels): collapse the stats row into a Stats hover chip (R13 S3)
The six inline catalog stats crammed into the tab row become one compact "Stats" chip that reveals the numbers on hover — a new ChannelStatsPopover on the AnchoredPopover hover mode, one icon-per-row (channels, recent synced, full history, [ETA], my videos, quota left). Kept in the manager's own toolbar (the header SyncStatus is global; these are channel-catalog figures). Retires the local Stat helper + the now-unused formatEta import. i18n channels.stats.label (HU+EN). Self-E2E'd: hover reveals the list. gate green (tsc/eslint-0/prettier).
This commit is contained in:
@@ -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 (
|
||||
<>
|
||||
<button
|
||||
ref={pop.refs.setReference}
|
||||
{...pop.getReferenceProps()}
|
||||
className="glass-card glass-hover inline-flex items-center gap-1.5 rounded-lg px-2.5 py-1.5 text-sm text-muted transition"
|
||||
>
|
||||
<BarChart3 className="h-4 w-4" aria-hidden="true" />
|
||||
{t("channels.stats.label")}
|
||||
</button>
|
||||
<PopoverSurface
|
||||
popover={pop}
|
||||
ariaLabel={t("channels.stats.label")}
|
||||
className="glass-menu w-60 rounded-xl p-2 text-xs animate-[popIn_0.16s_ease]"
|
||||
>
|
||||
{rows.map((r) => (
|
||||
<div key={r.label} className="flex items-center gap-2.5 px-1.5 py-1.5">
|
||||
<r.icon className="h-4 w-4 shrink-0 text-muted" aria-hidden="true" />
|
||||
<span className="flex-1 text-muted">{r.label}</span>
|
||||
<span className="tabular-nums font-medium text-fg">{r.value}</span>
|
||||
</div>
|
||||
))}
|
||||
</PopoverSurface>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user