feat(usage): turn refresh interval into a select and align control widths

Replace the click-to-cycle refresh button with a Select matching the
source/model filters. The "off" option now shows a localized label
(zh/en/ja/zh-TW) instead of the cryptic "--", and changing the interval
still invalidates all usage queries for an immediate refresh.

Align the top-bar controls into two width groups: source and model
selects at 120px, refresh select and date range trigger at 150px. The
date range button moves from auto width to fixed, so its long custom
range label gets truncate + hover title, and the calendar icon is
shrink-proofed.
This commit is contained in:
Jason
2026-06-11 16:29:43 +08:00
Unverified
parent a75f479576
commit 22ecd2d611
6 changed files with 37 additions and 22 deletions
+26 -20
View File
@@ -21,7 +21,6 @@ import {
LayoutGrid,
} from "lucide-react";
import { ProviderIcon } from "@/components/ProviderIcon";
import { Button } from "@/components/ui/button";
import {
Select,
SelectContent,
@@ -47,6 +46,9 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
const APP_FILTER_OPTIONS: AppTypeFilter[] = ["all", ...KNOWN_APP_TYPES];
// 0 表示关闭自动刷新(refetchInterval=false
const REFRESH_INTERVAL_OPTIONS_MS = [0, 5000, 10000, 30000, 60000] as const;
// 与 AppSwitcher 的 appIconName 保持一致(codex 复用 openai 图标)
const APP_FILTER_ICON: Record<AppType, string> = {
claude: "claude",
@@ -93,14 +95,7 @@ export function UsageDashboard() {
// usage 查询,实现实时刷新(仅在 Dashboard 挂载时生效,离开页面自动取消监听)
useUsageEventBridge();
const refreshIntervalOptionsMs = [0, 5000, 10000, 30000, 60000] as const;
const changeRefreshInterval = () => {
const currentIndex = refreshIntervalOptionsMs.indexOf(
refreshIntervalMs as (typeof refreshIntervalOptionsMs)[number],
);
const safeIndex = currentIndex >= 0 ? currentIndex : 3;
const nextIndex = (safeIndex + 1) % refreshIntervalOptionsMs.length;
const next = refreshIntervalOptionsMs[nextIndex];
const changeRefreshInterval = (next: number) => {
setRefreshIntervalMs(next);
queryClient.invalidateQueries({ queryKey: usageKeys.all });
};
@@ -211,7 +206,7 @@ export function UsageDashboard() {
onValueChange={(v) => changeProviderName(decodeOptionValue(v))}
>
<SelectTrigger
className="h-9 w-[110px] bg-background text-xs [&>span]:min-w-0 [&>span]:truncate"
className="h-9 w-[120px] bg-background text-xs [&>span]:min-w-0 [&>span]:truncate"
title={providerName ?? t("usage.filterBySource")}
>
<SelectValue />
@@ -257,17 +252,28 @@ export function UsageDashboard() {
</Select>
<div className="flex items-center gap-2 ml-auto lg:ml-0">
<Button
type="button"
variant="outline"
size="sm"
className="h-9 px-3 text-xs"
title={t("common.refresh", "刷新")}
onClick={changeRefreshInterval}
<Select
value={String(refreshIntervalMs)}
onValueChange={(v) => changeRefreshInterval(Number(v))}
>
<RefreshCw className="mr-2 h-3.5 w-3.5" />
{refreshIntervalMs > 0 ? `${refreshIntervalMs / 1000}s` : "--"}
</Button>
<SelectTrigger
className="h-9 w-[150px] bg-background text-xs"
title={t("usage.refreshInterval")}
aria-label={t("usage.refreshInterval")}
>
<span className="flex items-center gap-2">
<RefreshCw className="h-3.5 w-3.5 shrink-0" />
<SelectValue />
</span>
</SelectTrigger>
<SelectContent>
{REFRESH_INTERVAL_OPTIONS_MS.map((ms) => (
<SelectItem key={ms} value={String(ms)}>
{ms > 0 ? `${ms / 1000}s` : t("usage.refreshOff")}
</SelectItem>
))}
</SelectContent>
</Select>
<UsageDateRangePicker
selection={range}
@@ -273,9 +273,10 @@ export function UsageDateRangePicker({
<Button
type="button"
variant={selection.preset === "custom" ? "default" : "outline"}
className="justify-start gap-2"
className="w-[150px] justify-start gap-2"
title={triggerLabel}
>
<CalendarDays className="h-4 w-4" />
<CalendarDays className="h-4 w-4 shrink-0" />
<span className="truncate">{triggerLabel}</span>
</Button>
</PopoverTrigger>
+2
View File
@@ -1446,6 +1446,8 @@
"allModels": "All Models",
"filterBySource": "Filter by source",
"filterByModel": "Filter by model",
"refreshInterval": "Auto-refresh interval",
"refreshOff": "Off",
"timeRange": "Time Range",
"customRange": "Calendar Filter",
"customRangeHint": "Supports both date and time",
+2
View File
@@ -1446,6 +1446,8 @@
"allModels": "すべてのモデル",
"filterBySource": "ソースで絞り込む",
"filterByModel": "モデルで絞り込む",
"refreshInterval": "自動更新間隔",
"refreshOff": "オフ",
"timeRange": "期間",
"customRange": "カレンダーフィルター",
"customRangeHint": "日付と時刻の両方に対応",
+2
View File
@@ -1418,6 +1418,8 @@
"allModels": "全部模型",
"filterBySource": "依來源篩選",
"filterByModel": "依模型篩選",
"refreshInterval": "自動重新整理間隔",
"refreshOff": "關閉",
"timeRange": "時間範圍",
"customRange": "日曆篩選",
"customRangeHint": "支援日期與時間",
+2
View File
@@ -1446,6 +1446,8 @@
"allModels": "全部模型",
"filterBySource": "按来源筛选",
"filterByModel": "按模型筛选",
"refreshInterval": "自动刷新间隔",
"refreshOff": "关闭",
"timeRange": "时间范围",
"customRange": "日历筛选",
"customRangeHint": "支持日期与时间",