✨ 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:
+26
-78
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user