diff --git a/src/pages/UsagePage.module.scss b/src/pages/UsagePage.module.scss index 062a923..1b56d82 100644 --- a/src/pages/UsagePage.module.scss +++ b/src/pages/UsagePage.module.scss @@ -44,38 +44,94 @@ align-items: center; } -.select.timeRangeSelect { +.timeRangeSelect { + display: inline-flex; + align-items: center; + justify-content: space-between; + gap: 8px; min-width: 164px; height: 40px; - border-color: var(--border-color); + padding: 0 12px; + border: 1px solid var(--border-color); border-radius: $radius-md; background-color: var(--bg-primary); box-shadow: var(--shadow); color: var(--text-primary); font-size: 13px; font-weight: 500; - padding-right: 34px; + cursor: pointer; appearance: none; - -webkit-appearance: none; - -moz-appearance: none; + text-align: left; &:hover { border-color: var(--border-hover); } &:focus { + outline: none; + box-shadow: var(--shadow), 0 0 0 3px rgba(59, 130, 246, 0.15); + } + + &[aria-expanded='true'] { + border-color: var(--primary-color); box-shadow: var(--shadow), 0 0 0 3px rgba(59, 130, 246, 0.15); } } +.timeRangeSelectedText { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + .timeRangeSelectIcon { - position: absolute; - right: 11px; - top: 50%; - transform: translateY(-50%); display: inline-flex; color: var(--text-secondary); - pointer-events: none; + flex-shrink: 0; + transition: transform 0.2s ease; + + [aria-expanded='true'] > & { + transform: rotate(180deg); + } +} + +.timeRangeDropdown { + position: absolute; + top: calc(100% + 6px); + left: 0; + right: 0; + z-index: 1000; + background: var(--bg-primary); + border: 1px solid var(--border-color); + border-radius: $radius-lg; + padding: 6px; + box-shadow: var(--shadow-lg); + display: flex; + flex-direction: column; + gap: 4px; +} + +.timeRangeOption { + padding: 8px 12px; + border-radius: $radius-md; + border: 1px solid transparent; + background: transparent; + color: var(--text-primary); + cursor: pointer; + text-align: left; + font-size: 13px; + font-weight: 500; + transition: background-color 0.15s ease, border-color 0.15s ease; + + &:hover { + background: var(--bg-secondary); + } +} + +.timeRangeOptionActive { + border-color: rgba(59, 130, 246, 0.5); + background: rgba(59, 130, 246, 0.10); + font-weight: 600; } .pageTitle { diff --git a/src/pages/UsagePage.tsx b/src/pages/UsagePage.tsx index daf081a..3e53983 100644 --- a/src/pages/UsagePage.tsx +++ b/src/pages/UsagePage.tsx @@ -1,4 +1,4 @@ -import { useState, useMemo, useCallback, useEffect } from 'react'; +import { useState, useMemo, useCallback, useEffect, useRef } from 'react'; import { useTranslation } from 'react-i18next'; import { Chart as ChartJS, @@ -54,6 +54,12 @@ const TIME_RANGE_STORAGE_KEY = 'cli-proxy-usage-time-range-v1'; const DEFAULT_CHART_LINES = ['all']; const DEFAULT_TIME_RANGE: UsageTimeRange = '24h'; const MAX_CHART_LINES = 9; +const TIME_RANGE_OPTIONS: ReadonlyArray<{ value: UsageTimeRange; labelKey: string }> = [ + { value: 'all', labelKey: 'usage_stats.range_all' }, + { value: '7h', labelKey: 'usage_stats.range_7h' }, + { value: '24h', labelKey: 'usage_stats.range_24h' }, + { value: '7d', labelKey: 'usage_stats.range_7d' }, +]; const HOUR_WINDOW_BY_TIME_RANGE: Record, number> = { '7h': 7, '24h': 24, @@ -110,6 +116,19 @@ export function UsagePage() { const resolvedTheme = useThemeStore((state) => state.resolvedTheme); const isDark = resolvedTheme === 'dark'; + // Time range dropdown + const [timeRangeOpen, setTimeRangeOpen] = useState(false); + const timeRangeRef = useRef(null); + + useEffect(() => { + if (!timeRangeOpen) return; + const handleClickOutside = (event: MouseEvent) => { + if (!timeRangeRef.current?.contains(event.target as Node)) setTimeRangeOpen(false); + }; + document.addEventListener('mousedown', handleClickOutside); + return () => document.removeEventListener('mousedown', handleClickOutside); + }, [timeRangeOpen]); + // Data hook const { usage, @@ -214,20 +233,43 @@ export function UsagePage() {
{t('usage_stats.range_filter')} -
- - + + {t(TIME_RANGE_OPTIONS.find((o) => o.value === timeRange)?.labelKey ?? 'usage_stats.range_24h')} + + + + {timeRangeOpen && ( +
+ {TIME_RANGE_OPTIONS.map((opt) => { + const active = opt.value === timeRange; + return ( + + ); + })} +
+ )}