import { useState } from "react"; import { useTranslation } from "react-i18next"; import { keepPreviousData, useQuery } from "@tanstack/react-query"; import { api, type AdminQuotaRow } from "../lib/api"; import { sinceOf } from "../lib/activityUrlState"; import { formatNumber, quotaActionLabel } from "../lib/format"; import { qk } from "../lib/queryKeys"; import type { Shared } from "./ActivityRuns"; import { Pressable } from "./ui/Pressable"; import { LoadingState, StateMessage } from "./ui/QueryState"; import { SURFACE } from "./ui/surfaces"; // Activity → Quota: the shared YouTube API budget's spend over the page's time range — per day // (Pacific days, as the budget resets), then per account, each opening to its actions. The // account filter narrows both to one account; the text filter has nothing to match here. export default function ActivityQuota({ shared }: { shared: Shared }) { const { t, i18n } = useTranslation(); const lang = i18n.language; const q = useQuery({ queryKey: qk.adminQuota({ window: shared.window, user: shared.user }), queryFn: () => api.adminQuota(sinceOf(shared.window), shared.user), placeholderData: keepPreviousData, }); const data = q.data; const maxDaily = Math.max(1, ...(data?.daily ?? []).map((d) => d.total)); const grandTotal = (data?.rows ?? []).reduce((s, r) => s + r.total, 0); return (

{t("stats.introBefore")} {t("stats.introSystem")} {t("stats.introAfter")}

{q.isError && !data ? ( void q.refetch()}> {t("common.loadError")} ) : q.isLoading || !data ? ( ) : ( <> {q.isError && ( void q.refetch()} className="mb-3"> {t("activity.stale")} )}
{t(shared.window === "all" ? "activity.quota.totalAll" : "activity.quota.total", { count: grandTotal, window: t(`activity.windows.${shared.window}`), units: formatNumber(grandTotal, lang), })}
{data.daily.length === 0 ? (
{t("stats.noUsageInRange")}
) : ( // Each day's figure is in its bar's text too (read out, not shown): the tooltip only // reaches a pointer.
{data.daily.map((d) => (
{t("stats.dailyTooltip", { count: d.total, day: d.day, units: formatNumber(d.total, lang), })}
))}
)}
{data.rows.length === 0 ? (
{t("stats.noUsageInRange")}
) : ( data.rows.map((r) => ( )) )}
)}
); } function UserRow({ row, max }: { row: AdminQuotaRow; max: number }) { const { t, i18n } = useTranslation(); const lang = i18n.language; const [open, setOpen] = useState(false); const actions = Object.entries(row.by_action).sort((a, b) => b[1] - a[1]); const isSystem = row.user_id === null; return (
setOpen((o) => !o)} aria-expanded={open} className="w-full flex items-center gap-3" > {isSystem ? t("stats.system") : row.email} {formatNumber(row.total, lang)} {/* Proportion bar */}
{open && actions.length > 0 && (
{actions.map(([action, units]) => (
{quotaActionLabel(t, action)} {formatNumber(units, lang)}
))}
)}
); }