✨ refactor(ui): Improve usage log filter responsiveness and mobile UX
Refactor the usage log filter toolbar into a shared reusable component for common, drawing, and task logs. Optimize desktop filters with a responsive grid, move secondary filters into a mobile drawer, standardize filter typography, remove redundant filter icons, and add the missing i18n translations for the new drawer description.
This commit is contained in:
+4
-5
@@ -30,7 +30,6 @@ import {
|
||||
FileText,
|
||||
KeyRound,
|
||||
ListChecks,
|
||||
Play,
|
||||
RadioTower,
|
||||
ShieldCheck,
|
||||
TerminalSquare,
|
||||
@@ -505,10 +504,10 @@ export function OverviewDashboard() {
|
||||
const quickActions = useMemo<QuickAction[]>(
|
||||
() => [
|
||||
{
|
||||
title: t('Playground'),
|
||||
description: t('Test models and prompts from the browser'),
|
||||
to: '/playground',
|
||||
icon: Play,
|
||||
title: t('API Keys'),
|
||||
description: t('Create a key for your app or service'),
|
||||
to: '/keys',
|
||||
icon: KeyRound,
|
||||
},
|
||||
{
|
||||
title: t('Channels'),
|
||||
|
||||
@@ -26,6 +26,7 @@ import { VCHART_OPTION } from '@/lib/vchart'
|
||||
import { useThemeCustomization } from '@/context/theme-customization-provider'
|
||||
import { useTheme } from '@/context/theme-provider'
|
||||
import { Skeleton } from '@/components/ui/skeleton'
|
||||
import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||
import { getUserQuotaDataByUsers } from '@/features/dashboard/api'
|
||||
import {
|
||||
TIME_GRANULARITY_OPTIONS,
|
||||
@@ -154,61 +155,64 @@ export function UserCharts() {
|
||||
return (
|
||||
<div className='space-y-3'>
|
||||
<div className='flex items-center gap-1.5 overflow-x-auto pb-1 sm:gap-2'>
|
||||
<div className='flex shrink-0 items-center gap-1.5 rounded-lg border p-0.5'>
|
||||
{TIME_RANGE_PRESETS.map((preset) => (
|
||||
<button
|
||||
key={preset.days}
|
||||
type='button'
|
||||
onClick={() => handleRangeChange(preset.days)}
|
||||
className={`rounded-md px-2.5 py-1 text-xs font-medium transition-colors ${
|
||||
selectedRange === preset.days
|
||||
? 'bg-primary text-primary-foreground shadow-sm'
|
||||
: 'text-muted-foreground hover:bg-muted hover:text-foreground'
|
||||
}`}
|
||||
>
|
||||
{t(preset.label)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<Tabs
|
||||
value={String(selectedRange)}
|
||||
onValueChange={(value) => handleRangeChange(Number(value))}
|
||||
className='shrink-0'
|
||||
>
|
||||
<TabsList>
|
||||
{TIME_RANGE_PRESETS.map((preset) => (
|
||||
<TabsTrigger
|
||||
key={preset.days}
|
||||
value={String(preset.days)}
|
||||
className='px-2.5 text-xs'
|
||||
>
|
||||
{t(preset.label)}
|
||||
</TabsTrigger>
|
||||
))}
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
|
||||
<div className='flex shrink-0 items-center gap-1.5 rounded-lg border p-0.5'>
|
||||
{TIME_GRANULARITY_OPTIONS.map((opt) => (
|
||||
<button
|
||||
key={opt.value}
|
||||
type='button'
|
||||
onClick={() =>
|
||||
handleGranularityChange(opt.value as TimeGranularity)
|
||||
}
|
||||
className={`rounded-md px-2.5 py-1 text-xs font-medium transition-colors ${
|
||||
timeGranularity === opt.value
|
||||
? 'bg-primary text-primary-foreground shadow-sm'
|
||||
: 'text-muted-foreground hover:bg-muted hover:text-foreground'
|
||||
}`}
|
||||
>
|
||||
{t(opt.label)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<Tabs
|
||||
value={timeGranularity}
|
||||
onValueChange={(value) =>
|
||||
handleGranularityChange(value as TimeGranularity)
|
||||
}
|
||||
className='shrink-0'
|
||||
>
|
||||
<TabsList>
|
||||
{TIME_GRANULARITY_OPTIONS.map((opt) => (
|
||||
<TabsTrigger
|
||||
key={opt.value}
|
||||
value={opt.value}
|
||||
className='px-2.5 text-xs'
|
||||
>
|
||||
{t(opt.label)}
|
||||
</TabsTrigger>
|
||||
))}
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
|
||||
<div className='flex shrink-0 items-center gap-1.5 rounded-lg border p-0.5'>
|
||||
<span className='text-muted-foreground px-2 text-xs font-medium'>
|
||||
{t('Top Users')}
|
||||
</span>
|
||||
{TOP_USER_LIMIT_OPTIONS.map((limit) => (
|
||||
<button
|
||||
key={limit}
|
||||
type='button'
|
||||
onClick={() => setTopUserLimit(limit)}
|
||||
className={`rounded-md px-2.5 py-1 text-xs font-medium transition-colors ${
|
||||
topUserLimit === limit
|
||||
? 'bg-primary text-primary-foreground shadow-sm'
|
||||
: 'text-muted-foreground hover:bg-muted hover:text-foreground'
|
||||
}`}
|
||||
>
|
||||
{t('Top {{count}}', { count: limit })}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<Tabs
|
||||
value={String(topUserLimit)}
|
||||
onValueChange={(value) => setTopUserLimit(Number(value))}
|
||||
className='shrink-0'
|
||||
>
|
||||
<TabsList>
|
||||
<span className='text-muted-foreground px-2 text-xs font-medium whitespace-nowrap'>
|
||||
{t('Top Users')}
|
||||
</span>
|
||||
{TOP_USER_LIMIT_OPTIONS.map((limit) => (
|
||||
<TabsTrigger
|
||||
key={limit}
|
||||
value={String(limit)}
|
||||
className='px-2.5 text-xs'
|
||||
>
|
||||
{t('Top {{count}}', { count: limit })}
|
||||
</TabsTrigger>
|
||||
))}
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
|
||||
{isLoading && (
|
||||
<Loader2 className='text-muted-foreground size-4 animate-spin' />
|
||||
|
||||
Reference in New Issue
Block a user