feat: enhance UI and functionality in various components

This commit is contained in:
CaIon
2026-04-28 18:38:02 +08:00
parent fc377dae3e
commit 28f7e9eb2e
40 changed files with 1567 additions and 470 deletions
@@ -8,6 +8,7 @@ import {
formatLogQuota,
formatTimestampToDate,
} from '@/lib/format'
import { getAvatarColorClass } from '@/lib/colors'
import { cn } from '@/lib/utils'
import { Button } from '@/components/ui/button'
import {
@@ -241,19 +242,6 @@ export function useCommonLogsColumns(isAdmin: boolean): ColumnDef<UsageLog>[] {
size='sm'
copyable={false}
/>
{log.request_id && (
<StatusBadge
label={
log.request_id.length > 18
? `${log.request_id.slice(0, 18)}`
: log.request_id
}
variant='neutral'
size='sm'
copyText={log.request_id}
className='max-w-[140px] truncate font-mono'
/>
)}
</div>
)
},
@@ -267,45 +255,47 @@ export function useCommonLogsColumns(isAdmin: boolean): ColumnDef<UsageLog>[] {
]
if (isAdmin) {
columns.push({
id: 'source',
header: ({ column }) => (
<DataTableColumnHeader column={column} title={t('Source')} />
),
cell: function SourceCell({ row }) {
const {
setAffinityTarget,
setAffinityDialogOpen,
setSelectedUserId,
setUserInfoDialogOpen,
} = useUsageLogsContext()
const log = row.original
columns.push(
{
id: 'channel',
header: ({ column }) => (
<DataTableColumnHeader column={column} title={t('Channel')} />
),
cell: function ChannelCell({ row }) {
const {
sensitiveVisible,
setAffinityTarget,
setAffinityDialogOpen,
} = useUsageLogsContext()
const log = row.original
if (!isDisplayableLogType(log.type)) return null
if (!isDisplayableLogType(log.type)) return null
const other = parseLogOther(log.other)
const affinity = other?.admin_info?.channel_affinity
const useChannel = other?.admin_info?.use_channel
const channelChain =
useChannel && useChannel.length > 0
? useChannel.join(' → ')
: undefined
const channelDisplay = log.channel_name
? `${log.channel_name} #${log.channel}`
: `#${log.channel}`
const other = parseLogOther(log.other)
const affinity = other?.admin_info?.channel_affinity
const useChannel = other?.admin_info?.use_channel
const channelChain =
useChannel && useChannel.length > 0
? useChannel.join(' → ')
: undefined
const channelDisplay = log.channel_name
? `${log.channel_name} #${log.channel}`
: `#${log.channel}`
const channelIdDisplay = `#${log.channel}`
const channelName = sensitiveVisible ? log.channel_name : '••••'
return (
<div className='flex flex-col gap-1'>
<div className='flex items-center gap-1'>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<div className='relative'>
return (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<div className='flex max-w-[160px] flex-col gap-0.5'>
<div className='relative inline-flex w-fit'>
<StatusBadge
label={channelDisplay}
autoColor={log.channel_name || String(log.channel)}
label={channelIdDisplay}
autoColor={String(log.channel)}
copyText={String(log.channel)}
size='sm'
className='font-mono'
/>
{affinity && (
<button
@@ -329,57 +319,89 @@ export function useCommonLogsColumns(isAdmin: boolean): ColumnDef<UsageLog>[] {
</button>
)}
</div>
</TooltipTrigger>
<TooltipContent>
<div className='space-y-1'>
<p>{channelDisplay}</p>
{channelChain && (
<p className='text-muted-foreground text-xs'>
{t('Chain')}: {channelChain}
{log.channel_name && (
<span className='text-muted-foreground/70 truncate text-[11px]'>
{channelName}
</span>
)}
</div>
</TooltipTrigger>
<TooltipContent>
<div className='space-y-1'>
<p>{sensitiveVisible ? channelDisplay : channelIdDisplay}</p>
{channelChain && (
<p className='text-muted-foreground text-xs'>
{t('Chain')}: {channelChain}
</p>
)}
{affinity && (
<div className='border-t pt-1 text-xs'>
<p className='font-medium'>{t('Channel Affinity')}</p>
<p>
{t('Rule')}: {affinity.rule_name || '-'}
</p>
)}
{affinity && (
<div className='border-t pt-1 text-xs'>
<p className='font-medium'>{t('Channel Affinity')}</p>
<p>
{t('Rule')}: {affinity.rule_name || '-'}
</p>
<p>
{t('Group')}:{' '}
{affinity.using_group ||
<p>
{t('Group')}:{' '}
{sensitiveVisible
? affinity.using_group ||
affinity.selected_group ||
'-'}
</p>
</div>
)}
</div>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
{log.username && (
<button
type='button'
className='flex items-center gap-1 text-left'
onClick={(e) => {
e.stopPropagation()
setSelectedUserId(log.user_id)
setUserInfoDialogOpen(true)
}}
>
<span className='bg-primary/10 text-primary flex size-4 items-center justify-center rounded-full text-[10px] font-bold'>
{log.username.charAt(0).toUpperCase()}
</span>
<span className='text-muted-foreground truncate text-xs hover:underline'>
{log.username}
</span>
</button>
)}
</div>
)
'-'
: '••••'}
</p>
</div>
)}
</div>
</TooltipContent>
</Tooltip>
</TooltipProvider>
)
},
meta: { label: t('Channel'), mobileHidden: true },
},
meta: { label: t('Source'), mobileHidden: true },
})
{
id: 'user',
header: ({ column }) => (
<DataTableColumnHeader column={column} title={t('User')} />
),
cell: function UserCell({ row }) {
const {
sensitiveVisible,
setSelectedUserId,
setUserInfoDialogOpen,
} = useUsageLogsContext()
const log = row.original
if (!isDisplayableLogType(log.type) || !log.username) return null
return (
<button
type='button'
className='flex items-center gap-1.5 text-left'
onClick={(e) => {
e.stopPropagation()
setSelectedUserId(log.user_id)
setUserInfoDialogOpen(true)
}}
>
<span
className={cn(
'flex size-5 items-center justify-center rounded-full text-[11px] font-bold',
sensitiveVisible
? getAvatarColorClass(log.username)
: 'bg-muted text-muted-foreground'
)}
>
{sensitiveVisible ? log.username.charAt(0).toUpperCase() : '•'}
</span>
<span className='text-muted-foreground truncate text-sm hover:underline'>
{sensitiveVisible ? log.username : '••••'}
</span>
</button>
)
},
meta: { label: t('User'), mobileHidden: true },
}
)
}
columns.push(
@@ -389,6 +411,7 @@ export function useCommonLogsColumns(isAdmin: boolean): ColumnDef<UsageLog>[] {
<DataTableColumnHeader column={column} title={t('Model')} />
),
cell: function ModelCell({ row }) {
const { sensitiveVisible } = useUsageLogsContext()
const log = row.original
if (!isDisplayableLogType(log.type)) return null
@@ -450,8 +473,8 @@ export function useCommonLogsColumns(isAdmin: boolean): ColumnDef<UsageLog>[] {
)
const metaParts: string[] = []
if (tokenName) metaParts.push(tokenName)
if (group) metaParts.push(group)
if (tokenName) metaParts.push(sensitiveVisible ? tokenName : '••••')
if (group) metaParts.push(sensitiveVisible ? group : '••••')
return (
<div className='flex max-w-[220px] flex-col gap-0.5'>
@@ -0,0 +1,279 @@
import { useState, useEffect, useCallback, type ReactNode } from 'react'
import { useNavigate, getRouteApi } from '@tanstack/react-router'
import { ChevronDown, Eye, EyeOff, RotateCcw, Search } from 'lucide-react'
import { useTranslation } from 'react-i18next'
import { cn } from '@/lib/utils'
import { useIsAdmin } from '@/hooks/use-admin'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select'
import { LOG_TYPES } from '../constants'
import { buildSearchParams } from '../lib/filter'
import { getDefaultTimeRange } from '../lib/utils'
import type { CommonLogFilters } from '../types'
import { CompactDateTimeRangePicker } from './compact-date-time-range-picker'
import { useUsageLogsContext } from './usage-logs-provider'
const route = getRouteApi('/_authenticated/usage-logs/$section')
interface CommonLogsFilterBarProps {
stats?: ReactNode
viewOptions?: ReactNode
}
export function CommonLogsFilterBar({
stats,
viewOptions,
}: CommonLogsFilterBarProps) {
const { t } = useTranslation()
const navigate = useNavigate()
const searchParams = route.useSearch()
const isAdmin = useIsAdmin()
const { sensitiveVisible, setSensitiveVisible } = useUsageLogsContext()
const [expanded, setExpanded] = useState(false)
const [filters, setFilters] = useState<CommonLogFilters>(() => {
const { start, end } = getDefaultTimeRange()
return { startTime: start, endTime: end }
})
const [logType, setLogType] = useState<string>('')
useEffect(() => {
const next: Partial<CommonLogFilters> = {}
if (searchParams.startTime)
next.startTime = new Date(searchParams.startTime)
if (searchParams.endTime) next.endTime = new Date(searchParams.endTime)
if (searchParams.channel) next.channel = String(searchParams.channel)
if (searchParams.model) next.model = searchParams.model
if (searchParams.token) next.token = searchParams.token
if (searchParams.group) next.group = searchParams.group
if (searchParams.username) next.username = searchParams.username
if (searchParams.requestId) next.requestId = searchParams.requestId
if (Object.keys(next).length > 0) {
setFilters((prev) => ({ ...prev, ...next }))
}
const typeArr = searchParams.type
if (Array.isArray(typeArr) && typeArr.length === 1) {
setLogType(typeArr[0])
}
}, [
searchParams.startTime,
searchParams.endTime,
searchParams.channel,
searchParams.model,
searchParams.token,
searchParams.group,
searchParams.username,
searchParams.requestId,
searchParams.type,
])
const handleChange = useCallback(
(field: keyof CommonLogFilters, value: Date | string | undefined) => {
setFilters((prev) => ({ ...prev, [field]: value }))
},
[]
)
const handleApply = useCallback(() => {
const filterParams = buildSearchParams(filters, 'common')
navigate({
to: '/usage-logs/$section',
params: { section: 'common' },
search: (prev: Record<string, unknown>) => ({
...prev,
...filterParams,
...(logType ? { type: [logType] } : { type: undefined }),
page: 1,
}),
})
}, [filters, logType, navigate])
const handleReset = useCallback(() => {
const { start, end } = getDefaultTimeRange()
const resetFilters: CommonLogFilters = { startTime: start, endTime: end }
setFilters(resetFilters)
setLogType('')
navigate({
to: '/usage-logs/$section',
params: { section: 'common' },
search: {
page: 1,
startTime: start.getTime(),
endTime: end.getTime(),
},
})
}, [navigate])
const handleKeyDown = useCallback(
(e: React.KeyboardEvent) => {
if (e.key === 'Enter') handleApply()
},
[handleApply]
)
const hasExpandedFilters =
!!filters.token ||
!!filters.username ||
!!filters.channel ||
!!filters.requestId
return (
<div className='space-y-3'>
{/* Primary filter row */}
<div className='grid grid-cols-2 gap-2 sm:grid-cols-4 lg:grid-cols-[minmax(280px,2fr)_minmax(140px,1fr)_minmax(120px,1fr)_minmax(120px,0.8fr)_auto]'>
<CompactDateTimeRangePicker
start={filters.startTime}
end={filters.endTime}
onChange={({ start, end }) => {
handleChange('startTime', start)
handleChange('endTime', end)
}}
className='col-span-2 lg:col-span-1'
/>
<Input
placeholder={t('Model Name')}
value={filters.model || ''}
onChange={(e) => handleChange('model', e.target.value)}
onKeyDown={handleKeyDown}
className='h-9'
/>
<Input
placeholder={t('Group')}
type={sensitiveVisible ? 'text' : 'password'}
value={filters.group || ''}
onChange={(e) => handleChange('group', e.target.value)}
onKeyDown={handleKeyDown}
className='h-9'
/>
<Select
value={logType}
onValueChange={(v) => setLogType(v === 'all' ? '' : v)}
>
<SelectTrigger className='h-9'>
<SelectValue placeholder={t('All Types')} />
</SelectTrigger>
<SelectContent>
<SelectItem value='all'>{t('All Types')}</SelectItem>
{LOG_TYPES.map((type) => (
<SelectItem key={type.value} value={String(type.value)}>
{t(type.label)}
</SelectItem>
))}
</SelectContent>
</Select>
<button
type='button'
className={cn(
'text-muted-foreground hover:text-foreground flex h-9 items-center gap-1 rounded-md px-2 text-xs transition-colors',
hasExpandedFilters && !expanded && 'text-primary'
)}
onClick={() => setExpanded((p) => !p)}
>
<ChevronDown
className={cn(
'size-3.5 transition-transform duration-200',
expanded && 'rotate-180'
)}
/>
{expanded ? t('Collapse') : t('Expand')}
</button>
</div>
{/* Expandable filter row */}
<div
className={cn(
'grid gap-2 overflow-hidden transition-all duration-200',
expanded
? 'grid-rows-[1fr] opacity-100'
: 'grid-rows-[0fr] opacity-0'
)}
>
<div className='min-h-0 overflow-hidden'>
<div className='grid grid-cols-2 gap-2 sm:grid-cols-4'>
<Input
placeholder={t('Token Name')}
type={sensitiveVisible ? 'text' : 'password'}
value={filters.token || ''}
onChange={(e) => handleChange('token', e.target.value)}
onKeyDown={handleKeyDown}
className='h-9'
/>
{isAdmin && (
<Input
placeholder={t('Username')}
type={sensitiveVisible ? 'text' : 'password'}
value={filters.username || ''}
onChange={(e) => handleChange('username', e.target.value)}
onKeyDown={handleKeyDown}
className='h-9'
/>
)}
{isAdmin && (
<Input
placeholder={t('Channel ID')}
value={filters.channel || ''}
onChange={(e) => handleChange('channel', e.target.value)}
onKeyDown={handleKeyDown}
className='h-9'
/>
)}
<Input
placeholder={t('Request ID')}
value={filters.requestId || ''}
onChange={(e) => handleChange('requestId', e.target.value)}
onKeyDown={handleKeyDown}
className='h-9'
/>
</div>
</div>
</div>
{/* Actions row */}
<div className='flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between'>
<div className='flex min-w-0 flex-wrap items-center gap-2 sm:gap-3'>
{stats && <div className='min-w-0'>{stats}</div>}
<button
type='button'
className='text-muted-foreground hover:text-foreground inline-flex h-6 items-center gap-1 rounded px-1 text-xs transition-colors'
title={sensitiveVisible ? t('Hide') : t('Show')}
aria-label={sensitiveVisible ? t('Hide') : t('Show')}
onClick={() => setSensitiveVisible(!sensitiveVisible)}
>
{sensitiveVisible ? (
<Eye className='size-3.5' />
) : (
<EyeOff className='size-3.5' />
)}
</button>
</div>
<div className='flex shrink-0 items-center gap-2 self-end sm:self-auto'>
<Button
variant='outline'
size='sm'
className='h-8'
onClick={handleReset}
>
<RotateCcw className='size-3.5' />
{t('Reset')}
</Button>
<Button size='sm' className='h-8' onClick={handleApply}>
<Search className='size-3.5' />
{t('Search')}
</Button>
{viewOptions}
</div>
</div>
</div>
)
}
@@ -5,10 +5,10 @@ import { formatLogQuota } from '@/lib/format'
import { cn } from '@/lib/utils'
import { useIsAdmin } from '@/hooks/use-admin'
import { Skeleton } from '@/components/ui/skeleton'
import { dotColorMap, textColorMap } from '@/components/status-badge'
import { getLogStats, getUserLogStats } from '../api'
import { DEFAULT_LOG_STATS } from '../constants'
import { buildApiParams } from '../lib/utils'
import { useUsageLogsContext } from './usage-logs-provider'
const route = getRouteApi('/_authenticated/usage-logs/$section')
@@ -16,6 +16,7 @@ export function CommonLogsStats() {
const { t } = useTranslation()
const isAdmin = useIsAdmin()
const searchParams = route.useSearch()
const { sensitiveVisible } = useUsageLogsContext()
const { data: stats, isLoading } = useQuery({
queryKey: ['usage-logs-stats', isAdmin, searchParams],
@@ -41,29 +42,42 @@ export function CommonLogsStats() {
if (isLoading) {
return (
<div className='flex items-center gap-2'>
<div className='flex items-center gap-1.5'>
<Skeleton className='h-6 w-[126px] rounded-md' />
<Skeleton className='h-6 w-[58px] rounded-md' />
<Skeleton className='h-6 w-[58px] rounded-md' />
<Skeleton className='h-6 w-[76px] rounded-md' />
<Skeleton className='h-6 w-[92px] rounded-md' />
</div>
)
}
const tagClass =
'inline-flex h-6 items-center rounded-md border px-2.5 text-xs font-medium shadow-xs'
return (
<div className='flex items-center gap-1.5 text-xs font-medium'>
<div className='flex flex-wrap items-center gap-1.5'>
<span
className={cn('size-1.5 shrink-0 rounded-full', dotColorMap.blue)}
aria-hidden='true'
/>
<span className={cn(textColorMap.blue)}>
{t('Usage')}: {formatLogQuota(stats?.quota || 0)}
className={cn(
tagClass,
'border-blue-200/70 bg-blue-50 text-blue-700 dark:border-blue-500/20 dark:bg-blue-500/10 dark:text-blue-300'
)}
>
{t('Usage')}:{' '}
{sensitiveVisible ? formatLogQuota(stats?.quota || 0) : '••••'}
</span>
<span className='text-muted-foreground/30'>·</span>
<span className={cn(textColorMap.pink)}>
<span
className={cn(
tagClass,
'border-pink-200/70 bg-pink-50 text-pink-700 dark:border-pink-500/20 dark:bg-pink-500/10 dark:text-pink-300'
)}
>
{t('RPM')}: {stats?.rpm || 0}
</span>
<span className='text-muted-foreground/30'>·</span>
<span className='text-muted-foreground'>
<span
className={cn(
tagClass,
'border-border bg-background text-muted-foreground'
)}
>
{t('TPM')}: {stats?.tpm || 0}
</span>
</div>
@@ -0,0 +1,199 @@
import { useMemo, useState } from 'react'
import { CalendarDays } from 'lucide-react'
import { useTranslation } from 'react-i18next'
import dayjs from '@/lib/dayjs'
import { cn } from '@/lib/utils'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import {
Popover,
PopoverContent,
PopoverTrigger,
} from '@/components/ui/popover'
interface CompactDateTimeRangePickerProps {
start?: Date
end?: Date
onChange: (range: { start?: Date; end?: Date }) => void
className?: string
}
function toInputValue(date?: Date): string {
return date ? dayjs(date).format('YYYY-MM-DDTHH:mm') : ''
}
function fromInputValue(value: string): Date | undefined {
if (!value) return undefined
const date = new Date(value)
return Number.isNaN(date.getTime()) ? undefined : date
}
export function CompactDateTimeRangePicker({
start,
end,
onChange,
className,
}: CompactDateTimeRangePickerProps) {
const { t } = useTranslation()
const [open, setOpen] = useState(false)
const [draftStart, setDraftStart] = useState(toInputValue(start))
const [draftEnd, setDraftEnd] = useState(toInputValue(end))
const label = useMemo(() => {
if (!start && !end) return t('Date Range')
const startText = start ? dayjs(start).format('YYYY-MM-DD HH:mm:ss') : '-'
const endText = end ? dayjs(end).format('YYYY-MM-DD HH:mm:ss') : '-'
return `${startText} ~ ${endText}`
}, [end, start, t])
const handleOpenChange = (nextOpen: boolean) => {
if (nextOpen) {
setDraftStart(toInputValue(start))
setDraftEnd(toInputValue(end))
}
setOpen(nextOpen)
}
const applyDraft = () => {
onChange({
start: fromInputValue(draftStart),
end: fromInputValue(draftEnd),
})
setOpen(false)
}
const applyPreset = (kind: 'today' | '7d' | 'week' | '30d' | 'month') => {
const now = dayjs()
const presets = {
today: {
start: now.startOf('day').toDate(),
end: now.endOf('day').toDate(),
},
'7d': {
start: now.subtract(6, 'day').startOf('day').toDate(),
end: now.endOf('day').toDate(),
},
week: {
start: now.startOf('week').toDate(),
end: now.endOf('week').toDate(),
},
'30d': {
start: now.subtract(29, 'day').startOf('day').toDate(),
end: now.endOf('day').toDate(),
},
month: {
start: now.startOf('month').toDate(),
end: now.endOf('month').toDate(),
},
}
const range = presets[kind]
setDraftStart(toInputValue(range.start))
setDraftEnd(toInputValue(range.end))
onChange(range)
setOpen(false)
}
return (
<Popover open={open} onOpenChange={handleOpenChange}>
<PopoverTrigger asChild>
<Button
type='button'
variant='outline'
className={cn(
'h-9 w-full justify-start gap-2 px-3 font-mono text-xs font-normal',
!start && !end && 'text-muted-foreground',
className
)}
>
<CalendarDays className='text-muted-foreground size-4 shrink-0' />
<span className='truncate'>{label}</span>
</Button>
</PopoverTrigger>
<PopoverContent align='start' className='w-[min(520px,calc(100vw-2rem))] p-3'>
<div className='space-y-3'>
<div className='grid gap-2 sm:grid-cols-[1fr_auto_1fr] sm:items-end'>
<div className='space-y-1.5'>
<div className='text-muted-foreground text-xs'>
{t('Start Time')}
</div>
<Input
type='datetime-local'
value={draftStart}
onChange={(e) => setDraftStart(e.target.value)}
className='h-8 font-mono text-xs'
/>
</div>
<span className='text-muted-foreground hidden pb-2 text-xs sm:block'>
~
</span>
<div className='space-y-1.5'>
<div className='text-muted-foreground text-xs'>
{t('End Time')}
</div>
<Input
type='datetime-local'
value={draftEnd}
onChange={(e) => setDraftEnd(e.target.value)}
className='h-8 font-mono text-xs'
/>
</div>
</div>
<div className='flex flex-wrap gap-1.5'>
<Button
type='button'
variant='secondary'
size='sm'
className='h-7 flex-1 px-2 text-xs'
onClick={() => applyPreset('today')}
>
{t('Today')}
</Button>
<Button
type='button'
variant='secondary'
size='sm'
className='h-7 flex-1 px-2 text-xs'
onClick={() => applyPreset('7d')}
>
{t('7 Days')}
</Button>
<Button
type='button'
variant='secondary'
size='sm'
className='h-7 flex-1 px-2 text-xs'
onClick={() => applyPreset('week')}
>
{t('This week')}
</Button>
<Button
type='button'
variant='secondary'
size='sm'
className='h-7 flex-1 px-2 text-xs'
onClick={() => applyPreset('30d')}
>
{t('30 Days')}
</Button>
<Button
type='button'
variant='secondary'
size='sm'
className='h-7 flex-1 px-2 text-xs'
onClick={() => applyPreset('month')}
>
{t('This month')}
</Button>
</div>
<div className='flex justify-end'>
<Button size='sm' className='h-8' onClick={applyDraft}>
{t('Confirm')}
</Button>
</div>
</div>
</PopoverContent>
</Popover>
)
}
@@ -11,6 +11,8 @@ interface UsageLogsContextValue {
setAffinityTarget: (target: ChannelAffinityInfo | null) => void
affinityDialogOpen: boolean
setAffinityDialogOpen: (open: boolean) => void
sensitiveVisible: boolean
setSensitiveVisible: (visible: boolean) => void
}
const UsageLogsContext = createContext<UsageLogsContextValue | undefined>(
@@ -23,6 +25,7 @@ export function UsageLogsProvider({ children }: { children: ReactNode }) {
const [affinityTarget, setAffinityTarget] =
useState<ChannelAffinityInfo | null>(null)
const [affinityDialogOpen, setAffinityDialogOpen] = useState(false)
const [sensitiveVisible, setSensitiveVisible] = useState(true)
return (
<UsageLogsContext.Provider
@@ -35,6 +38,8 @@ export function UsageLogsProvider({ children }: { children: ReactNode }) {
setAffinityTarget,
affinityDialogOpen,
setAffinityDialogOpen,
sensitiveVisible,
setSensitiveVisible,
}}
>
{children}
@@ -28,15 +28,18 @@ import {
import {
DataTablePagination,
DataTableToolbar,
DataTableViewOptions,
TableSkeleton,
TableEmpty,
MobileCardList,
} from '@/components/data-table'
import { PageFooterPortal } from '@/components/layout'
import { LOG_TYPE_FILTERS, DEFAULT_LOGS_DATA } from '../constants'
import { DEFAULT_LOGS_DATA } from '../constants'
import { useColumnsByCategory } from '../lib/columns'
import { fetchLogsByCategory } from '../lib/utils'
import type { LogCategory } from '../types'
import { CommonLogsFilterBar } from './common-logs-filter-bar'
import { CommonLogsStats } from './common-logs-stats'
const route = getRouteApi('/_authenticated/usage-logs/$section')
@@ -147,25 +150,23 @@ export function UsageLogsTable({ logCategory }: UsageLogsTableProps) {
ensurePageInRange(pageCount)
}, [pageCount, ensurePageInRange])
const filters =
logCategory === 'common'
? [
{
columnId: 'created_at',
title: t('Log Type'),
options: LOG_TYPE_FILTERS.map((opt) => ({
value: opt.value,
label: t(opt.label),
})),
singleSelect: true,
},
]
: []
return (
<>
<div className='space-y-4'>
<DataTableToolbar table={table} filters={filters} customSearch={null} />
{logCategory === 'common' ? (
<div className='rounded-md border bg-card/50 p-3 shadow-xs'>
<CommonLogsFilterBar
stats={<CommonLogsStats />}
viewOptions={<DataTableViewOptions table={table} />}
/>
</div>
) : (
<DataTableToolbar
table={table}
filters={[]}
customSearch={null}
/>
)}
{isMobile ? (
<MobileCardList
table={table}
+3 -3
View File
@@ -2,7 +2,6 @@ import { getRouteApi } from '@tanstack/react-router'
import { useTranslation } from 'react-i18next'
import { SectionPageLayout } from '@/components/layout'
import { CacheStatsDialog } from '@/features/system-settings/general/channel-affinity/cache-stats-dialog'
import { CommonLogsStats } from './components/common-logs-stats'
import { UserInfoDialog } from './components/dialogs/user-info-dialog'
import { UsageLogsPrimaryButtons } from './components/usage-logs-primary-buttons'
import {
@@ -60,8 +59,9 @@ function UsageLogsContent() {
{description}
</SectionPageLayout.Description>
<SectionPageLayout.Actions>
{activeCategory === 'common' && <CommonLogsStats />}
<UsageLogsPrimaryButtons logCategory={activeCategory} />
{activeCategory !== 'common' && (
<UsageLogsPrimaryButtons logCategory={activeCategory} />
)}
</SectionPageLayout.Actions>
<SectionPageLayout.Content>
<UsageLogsTable logCategory={activeCategory} />