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:
t0ng7u
2026-05-25 05:35:44 +08:00
parent b302be30e3
commit 583da45296
79 changed files with 1879 additions and 1262 deletions
@@ -41,7 +41,7 @@ import {
} from '@/components/ui/table'
import { Textarea } from '@/components/ui/textarea'
import { ConfirmDialog } from '@/components/confirm-dialog'
import { StatusBadge } from '@/components/status-badge'
import { StatusBadge, StatusBadgeList } from '@/components/status-badge'
import { SettingsSwitchField } from '../../components/settings-form-layout'
import { SettingsPageActionsPortal } from '../../components/settings-page-context'
import { SettingsSection } from '../../components/settings-section'
@@ -64,6 +64,24 @@ function parseRules(jsonStr: string): AffinityRule[] {
}
}
function RuleBadgeList(props: { items: string[] }) {
return (
<StatusBadgeList
items={props.items}
max={2}
getKey={(item) => item}
renderItem={(item) => (
<StatusBadge
label={item}
variant='neutral'
size='sm'
copyable={false}
/>
)}
/>
)
}
function serializeRules(rules: AffinityRule[]): string {
return JSON.stringify(rules.map(({ id: _, ...rest }) => rest))
}
@@ -500,65 +518,15 @@ export function ChannelAffinitySection(props: Props) {
{rule.name || '-'}
</TableCell>
<TableCell>
<div className='text-muted-foreground flex items-center gap-1.5 text-xs font-medium'>
{(rule.model_regex || []).length > 0 && (
<span
className='size-1.5 shrink-0 rounded-full bg-slate-400'
aria-hidden='true'
/>
)}
{(rule.model_regex || [])
.slice(0, 2)
.map((r, i, arr) => (
<span
key={i}
className='flex items-center gap-1.5'
>
{r}
{i < arr.length - 1 && (
<span className='text-muted-foreground/30'>
·
</span>
)}
</span>
))}
{(rule.model_regex || []).length > 2 && (
<span className='text-muted-foreground/50'>
+{(rule.model_regex || []).length - 2}
</span>
)}
</div>
<RuleBadgeList items={rule.model_regex || []} />
</TableCell>
<TableCell>
<div className='text-muted-foreground flex items-center gap-1.5 text-xs font-medium'>
{(rule.key_sources || []).length > 0 && (
<span
className='size-1.5 shrink-0 rounded-full bg-slate-400'
aria-hidden='true'
/>
<RuleBadgeList
items={(rule.key_sources || []).map(
(src) =>
`${src.type}:${src.type === 'gjson' ? src.path : src.key}`
)}
{(rule.key_sources || [])
.slice(0, 2)
.map((src, i, arr) => (
<span
key={i}
className='flex items-center gap-1.5'
>
{src.type}:
{src.type === 'gjson' ? src.path : src.key}
{i < arr.length - 1 && (
<span className='text-muted-foreground/30'>
·
</span>
)}
</span>
))}
{(rule.key_sources || []).length > 2 && (
<span className='text-muted-foreground/50'>
+{(rule.key_sources || []).length - 2}
</span>
)}
</div>
/>
</TableCell>
<TableCell>{rule.ttl_seconds || '-'}</TableCell>
<TableCell>
@@ -582,27 +550,7 @@ export function ChannelAffinitySection(props: Props) {
rule.include_rule_name && t('Rule'),
].filter(Boolean) as string[]
if (scopeItems.length === 0) return '-'
return (
<div className='text-muted-foreground flex items-center gap-1.5 text-xs font-medium'>
<span
className='size-1.5 shrink-0 rounded-full bg-slate-400'
aria-hidden='true'
/>
{scopeItems.map((item, idx, arr) => (
<span
key={idx}
className='flex items-center gap-1.5'
>
{item}
{idx < arr.length - 1 && (
<span className='text-muted-foreground/30'>
·
</span>
)}
</span>
))}
</div>
)
return <RuleBadgeList items={scopeItems} />
})()}
</TableCell>
<TableCell>