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
@@ -35,7 +35,7 @@ import {
formatQuota as formatQuotaValue,
} from '@/lib/format'
import { getLobeIcon } from '@/lib/lobe-icon'
import { cn, truncateText } from '@/lib/utils'
import { truncateText } from '@/lib/utils'
import { Button } from '@/components/ui/button'
import { Checkbox } from '@/components/ui/checkbox'
import {
@@ -47,11 +47,8 @@ import {
import { ConfirmDialog } from '@/components/confirm-dialog'
import { DataTableColumnHeader } from '@/components/data-table/column-header'
import { GroupBadge } from '@/components/group-badge'
import {
StatusBadge,
dotColorMap,
textColorMap,
} from '@/components/status-badge'
import { StatusBadge, StatusBadgeList } from '@/components/status-badge'
import { TableId } from '@/components/table-id'
import { TruncatedText } from '@/components/truncated-text'
import { getCodexUsage } from '../api'
import { CHANNEL_STATUS_CONFIG, MODEL_FETCHABLE_TYPES } from '../constants'
@@ -107,25 +104,12 @@ function renderLimitedItems(
items: React.ReactNode[],
maxDisplay: number = 2
): React.ReactNode {
if (items.length === 0)
return <span className='text-muted-foreground text-xs'>-</span>
const displayed = items.slice(0, maxDisplay)
const remaining = items.length - maxDisplay
return (
<div className='flex max-w-full items-center gap-1 overflow-hidden'>
{displayed}
{remaining > 0 && (
<StatusBadge
label={`+${remaining}`}
variant='neutral'
size='sm'
copyable={false}
className='flex-shrink-0'
/>
)}
</div>
<StatusBadgeList
items={items}
max={maxDisplay}
renderItem={(item) => item}
/>
)
}
@@ -361,47 +345,50 @@ function BalanceCell({ channel }: { channel: Channel }) {
return (
<TooltipProvider>
<div className='flex items-center gap-1.5 text-xs font-medium'>
<span
className={cn(
'size-1.5 shrink-0 rounded-full',
dotColorMap[isUpdating ? 'neutral' : variant]
)}
aria-hidden='true'
/>
<div className='flex items-center gap-1'>
<Tooltip>
<TooltipTrigger
render={<span className='text-muted-foreground cursor-help' />}
>
{usedDisplay}
</TooltipTrigger>
render={
<StatusBadge
label={usedDisplay}
variant='neutral'
size='sm'
copyable={false}
className='cursor-help'
/>
}
/>
<TooltipContent>
<p>
{t('Used:')} {usedDisplay}
</p>
</TooltipContent>
</Tooltip>
<span className='text-muted-foreground/30'>·</span>
<Tooltip>
<TooltipTrigger
render={
<span
className={cn(
'cursor-pointer transition-opacity hover:opacity-70',
<StatusBadge
label={
isUpdating
? t('Updating...')
: channel.type === 57
? t('Account Info')
: remainingDisplay
}
variant={
channel.type === 57
? 'text-primary'
: textColorMap[isUpdating ? 'neutral' : variant]
)}
? 'info'
: isUpdating
? 'neutral'
: variant
}
size='sm'
copyable={false}
className='cursor-pointer'
onClick={handleClickUpdate}
/>
}
>
{isUpdating
? 'Updating...'
: channel.type === 57
? t('Account Info')
: remainingDisplay}
</TooltipTrigger>
/>
<TooltipContent>
<p>
{channel.type === 57
@@ -491,15 +478,7 @@ export function useChannelsColumns(): ColumnDef<Channel>[] {
),
cell: ({ row }) => {
const id = row.getValue('id') as number
return (
<StatusBadge
label={String(id)}
variant='neutral'
copyText={String(id)}
size='sm'
className='font-mono'
/>
)
return <TableId value={id} />
},
size: 80,
},
@@ -695,8 +674,13 @@ export function useChannelsColumns(): ColumnDef<Channel>[] {
/>
}
>
<span className='text-muted-foreground/30'>·</span>
<span className={cn(textColorMap.purple)}>IO.NET</span>
<StatusBadge
label='IO.NET'
variant='purple'
size='sm'
copyable={false}
className='cursor-pointer'
/>
</TooltipTrigger>
<TooltipContent side='top'>
<div className='max-w-xs space-y-1'>
@@ -747,7 +731,6 @@ export function useChannelsColumns(): ColumnDef<Channel>[] {
<StatusBadge
label={`Active (${childrenCount})`}
variant='success'
showDot
size='sm'
copyable={false}
/>
@@ -806,7 +789,6 @@ export function useChannelsColumns(): ColumnDef<Channel>[] {
<StatusBadge
label={label}
variant={config.variant}
showDot={config.showDot}
size='sm'
copyable={false}
/>
@@ -835,7 +817,6 @@ export function useChannelsColumns(): ColumnDef<Channel>[] {
<StatusBadge
label={label}
variant={config.variant}
showDot={config.showDot}
size='sm'
copyable={false}
/>
@@ -75,6 +75,12 @@ import {
} from '@/components/ui/tooltip'
import { DataTableBulkActions as BulkActionsToolbar } from '@/components/data-table'
import { DataTablePagination } from '@/components/data-table/pagination'
import {
sideDrawerContentClassName,
sideDrawerFooterClassName,
sideDrawerFormClassName,
sideDrawerHeaderClassName,
} from '@/components/drawer-layout'
import { StatusBadge } from '@/components/status-badge'
import { formatResponseTime, handleTestChannel } from '../../lib'
import { useChannels } from '../channels-provider'
@@ -833,19 +839,19 @@ function FailureDetailsSheet({
side={isMobile ? 'bottom' : 'right'}
className={
isMobile
? 'max-h-[85dvh] gap-0 overflow-hidden rounded-t-xl p-0'
: 'h-dvh w-full gap-0 overflow-hidden p-0 sm:max-w-lg'
? sideDrawerContentClassName('h-auto max-h-[85dvh] rounded-t-xl')
: sideDrawerContentClassName('sm:max-w-lg')
}
>
{details && (
<>
<SheetHeader className='border-b px-4 py-3 text-start sm:px-5 sm:py-4'>
<SheetHeader className={sideDrawerHeaderClassName('sm:px-5')}>
<SheetTitle className='pr-10'>{t('Details')}</SheetTitle>
<SheetDescription className='pr-10 wrap-break-word'>
{details.model}
</SheetDescription>
</SheetHeader>
<div className='min-h-0 flex-1 space-y-4 overflow-y-auto px-4 py-3 sm:px-5 sm:py-4'>
<div className={sideDrawerFormClassName('gap-4 sm:px-5')}>
<section className='space-y-1'>
<div className='text-muted-foreground text-xs font-medium'>
{t('Model')}
@@ -869,7 +875,7 @@ function FailureDetailsSheet({
</pre>
</section>
</div>
<SheetFooter className='border-t px-4 py-3 sm:flex-row sm:justify-end sm:px-5'>
<SheetFooter className={sideDrawerFooterClassName('sm:px-5')}>
<Button
variant='outline'
className='w-full sm:w-auto'
@@ -99,6 +99,14 @@ import {
TooltipContent,
TooltipTrigger,
} from '@/components/ui/tooltip'
import {
sideDrawerContentClassName,
sideDrawerFooterClassName,
sideDrawerFormClassName,
sideDrawerHeaderClassName,
sideDrawerSectionClassName,
sideDrawerSwitchItemClassName,
} from '@/components/drawer-layout'
import { JsonEditor } from '@/components/json-editor'
import { MultiSelect } from '@/components/multi-select'
import {
@@ -269,9 +277,9 @@ function formatUnixTime(timestamp: unknown): string {
function CardHeading({ title, icon }: { title: string; icon?: ReactNode }) {
return (
<div className='flex items-center gap-2.5'>
<div className='flex items-center gap-3'>
{icon && (
<span className='bg-primary/10 text-primary flex h-8 w-8 items-center justify-center rounded-lg'>
<span className='bg-muted text-muted-foreground flex size-8 shrink-0 items-center justify-center rounded-md'>
{icon}
</span>
)}
@@ -1087,10 +1095,10 @@ export function ChannelMutateDrawer({
return (
<>
<Sheet open={open} onOpenChange={handleOpenChange}>
<SheetContent className='flex h-dvh w-full flex-col gap-0 overflow-hidden p-0 sm:max-w-3xl'>
<SheetHeader className='border-b px-4 py-3 text-start sm:px-6 sm:py-4'>
<SheetContent className={sideDrawerContentClassName('sm:max-w-3xl')}>
<SheetHeader className={sideDrawerHeaderClassName()}>
<SheetTitle className='flex items-center gap-3'>
<span className='bg-muted flex h-9 w-9 shrink-0 items-center justify-center rounded-xl border'>
<span className='bg-muted flex size-9 shrink-0 items-center justify-center rounded-md'>
{getLobeIcon(`${getChannelTypeIcon(currentType)}.Color`, 22)}
</span>
<span>
@@ -1115,10 +1123,10 @@ export function ChannelMutateDrawer({
<form
id='channel-form'
onSubmit={form.handleSubmit(onSubmit)}
className='flex-1 space-y-4 overflow-y-auto px-3 py-3 pb-4 sm:space-y-5 sm:px-4'
className={sideDrawerFormClassName('gap-5')}
>
{/* ── Basic Information ── */}
<div className='bg-card space-y-4 rounded-xl border p-3 sm:p-5'>
<div className={sideDrawerSectionClassName()}>
<CardHeading
title={t('Basic Information')}
icon={<Server className='h-4 w-4' />}
@@ -1173,8 +1181,8 @@ export function ChannelMutateDrawer({
control={form.control}
name='status'
render={({ field }) => (
<FormItem className='flex items-center justify-between rounded-lg border px-4 py-3'>
<div className='space-y-0.5'>
<FormItem className={sideDrawerSwitchItemClassName()}>
<div className='flex flex-col gap-0.5'>
<FormLabel>{t('Enabled')}</FormLabel>
<FormDescription className='text-xs'>
{t('Enable or disable this channel')}
@@ -1213,7 +1221,7 @@ export function ChannelMutateDrawer({
</div>
{/* ── API Access ── */}
<div className='bg-card space-y-4 rounded-xl border p-5'>
<div className={sideDrawerSectionClassName()}>
<CardHeading
title={t('API Access')}
icon={<Link2 className='h-4 w-4' />}
@@ -1945,7 +1953,7 @@ export function ChannelMutateDrawer({
</div>
</FormDescription>
{isEditing && (
<div className='mt-4 space-y-3 rounded-lg border border-dashed p-4'>
<div className='border-border/60 mt-4 flex flex-col gap-3 border-y border-dashed py-4'>
<div className='flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between'>
<div>
<p className='text-sm font-medium'>
@@ -2007,9 +2015,9 @@ export function ChannelMutateDrawer({
/>
{currentType === 57 && (
<div className='bg-muted/20 space-y-3 rounded-lg border p-4'>
<div className='border-border/60 flex flex-col gap-3 border-y py-4'>
<div className='flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between'>
<div className='space-y-0.5'>
<div className='flex flex-col gap-0.5'>
<div className='text-sm font-semibold'>
{t('Codex Authorization')}
</div>
@@ -2171,7 +2179,7 @@ export function ChannelMutateDrawer({
</div>
{/* ── Models & Groups ── */}
<div className='bg-card space-y-4 rounded-xl border p-5'>
<div className={sideDrawerSectionClassName()}>
<CardHeading
title={t('Models & Groups')}
icon={<Boxes className='h-4 w-4' />}
@@ -2429,11 +2437,11 @@ export function ChannelMutateDrawer({
render={
<button
type='button'
className='bg-card hover:bg-accent/50 flex w-full items-center justify-between rounded-xl border px-5 py-4 text-left transition-colors'
className='hover:bg-muted/40 flex w-full items-center justify-between rounded-md py-2 text-left transition-colors'
/>
}
>
<div className='space-y-0.5'>
<div className='flex flex-col gap-0.5'>
<div className='text-[13px] font-semibold'>
{t('Advanced Settings')}
</div>
@@ -2451,14 +2459,14 @@ export function ChannelMutateDrawer({
/>
</CollapsibleTrigger>
<CollapsibleContent className='mt-5 space-y-5'>
<CollapsibleContent className='mt-5 flex flex-col gap-5'>
{/* ── Routing & Overrides ── */}
<div className='bg-card space-y-4 rounded-xl border p-5'>
<div className={sideDrawerSectionClassName()}>
<CardHeading
title={t('Routing & Overrides')}
icon={<Route className='h-4 w-4' />}
/>
<div className='space-y-4'>
<div className='flex flex-col gap-4'>
<SubHeading
title={t('Routing Strategy')}
icon={<Route className='h-3.5 w-3.5' />}
@@ -2557,7 +2565,7 @@ export function ChannelMutateDrawer({
/>
</div>
<div className='space-y-4 border-t pt-4'>
<div className='flex flex-col gap-4 border-t pt-4'>
<SubHeading
title={t('Internal Notes')}
icon={<FileText className='h-3.5 w-3.5' />}
@@ -2606,7 +2614,7 @@ export function ChannelMutateDrawer({
</div>
</div>
<div className='space-y-4 border-t pt-4'>
<div className='flex flex-col gap-4 border-t pt-4'>
<SubHeading
title={t('Override Rules')}
icon={<Code className='h-3.5 w-3.5' />}
@@ -2848,13 +2856,13 @@ export function ChannelMutateDrawer({
</div>
{/* ── Extra Settings ── */}
<div className='bg-card space-y-4 rounded-xl border p-5'>
<div className={sideDrawerSectionClassName()}>
<CardHeading
title={t('Channel Extra Settings')}
icon={<Settings className='h-4 w-4' />}
/>
{(currentType === 1 || currentType === 14) && (
<div className='space-y-3 rounded-lg border p-4'>
<div className='border-border/60 flex flex-col gap-3 border-y py-4'>
<SubHeading
title={t('Field passthrough controls')}
icon={<SlidersHorizontal className='h-3.5 w-3.5' />}
@@ -3220,7 +3228,7 @@ export function ChannelMutateDrawer({
/>
{MODEL_FETCHABLE_TYPES.has(currentType) && (
<div className='space-y-3 rounded-lg border p-4'>
<div className='border-border/60 flex flex-col gap-3 border-y py-4'>
<SubHeading
title={t('Upstream Model Detection Settings')}
icon={<RefreshCw className='h-3.5 w-3.5' />}
@@ -3341,7 +3349,7 @@ export function ChannelMutateDrawer({
</form>
</Form>
<SheetFooter className='grid grid-cols-2 gap-2 border-t px-4 py-3 sm:flex sm:px-6 sm:py-4'>
<SheetFooter className={sideDrawerFooterClassName()}>
<SheetClose
render={<Button variant='outline' disabled={isSubmitting} />}
>