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
@@ -16,17 +16,11 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
import { useEffect, useState, type ReactNode } from 'react'
import { useEffect, useState } from 'react'
import { useForm, type SubmitErrorHandler } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { useQuery } from '@tanstack/react-query'
import {
ChevronDown,
KeyRound,
Settings2,
WalletCards,
type LucideIcon,
} from 'lucide-react'
import { ChevronDown, KeyRound, Settings2, WalletCards } from 'lucide-react'
import { useTranslation } from 'react-i18next'
import { toast } from 'sonner'
import { getUserModels, getUserGroups } from '@/lib/api'
@@ -61,6 +55,15 @@ import {
import { Switch } from '@/components/ui/switch'
import { Textarea } from '@/components/ui/textarea'
import { DateTimePicker } from '@/components/datetime-picker'
import {
SideDrawerSection,
SideDrawerSectionHeader,
sideDrawerContentClassName,
sideDrawerFooterClassName,
sideDrawerFormClassName,
sideDrawerHeaderClassName,
sideDrawerSwitchItemClassName,
} from '@/components/drawer-layout'
import { MultiSelect } from '@/components/multi-select'
import { createApiKey, updateApiKey, getApiKey } from '../api'
import { ERROR_MESSAGES, SUCCESS_MESSAGES } from '../constants'
@@ -82,42 +85,12 @@ type ApiKeyMutateDrawerProps = {
open: boolean
onOpenChange: (open: boolean) => void
currentRow?: ApiKey
side?: 'left' | 'right'
}
type ApiKeyFormSectionProps = {
title: string
description: string
icon: LucideIcon
children: ReactNode
}
function ApiKeyFormSection(props: ApiKeyFormSectionProps) {
const Icon = props.icon
return (
<section className='bg-card rounded-lg border'>
<div className='flex items-center gap-2.5 border-b px-3 py-2.5 sm:gap-3 sm:px-4 sm:py-3'>
<div className='bg-muted text-muted-foreground flex size-8 shrink-0 items-center justify-center rounded-lg border sm:size-10'>
<Icon className='size-4 sm:size-5' />
</div>
<div className='min-w-0'>
<h3 className='text-sm leading-none font-medium'>{props.title}</h3>
<p className='text-muted-foreground mt-0.5 text-xs sm:mt-1'>
{props.description}
</p>
</div>
</div>
<div className='space-y-3 p-3 sm:space-y-4 sm:p-4'>{props.children}</div>
</section>
)
}
export function ApiKeysMutateDrawer({
open,
onOpenChange,
currentRow,
side = 'right',
}: ApiKeyMutateDrawerProps) {
const { t } = useTranslation()
const isUpdate = !!currentRow
@@ -284,31 +257,30 @@ export function ApiKeysMutateDrawer({
}}
>
<SheetContent
side={side}
className='bg-background flex !h-dvh !w-screen max-w-none gap-0 overflow-hidden p-0 sm:!w-full sm:!max-w-[620px]'
className={sideDrawerContentClassName('max-w-none sm:!max-w-[620px]')}
>
<SheetHeader className='bg-background border-b px-4 py-3 text-start sm:px-5 sm:py-4'>
<SheetTitle className='text-base sm:text-lg'>
<SheetHeader className={sideDrawerHeaderClassName()}>
<SheetTitle>
{isUpdate ? t('Update API Key') : t('Create API Key')}
</SheetTitle>
<SheetDescription className='pr-6 text-xs sm:text-sm'>
<SheetDescription>
{isUpdate
? t('Update the API key by providing necessary info.')
: t('Add a new API key by providing necessary info.')}{' '}
{t("Click save when you're done.")}
: t('Add a new API key by providing necessary info.')}
</SheetDescription>
</SheetHeader>
<Form {...form}>
<form
id='api-key-form'
onSubmit={form.handleSubmit(onSubmit, onInvalid)}
className='min-h-0 flex-1 space-y-3 overflow-y-auto overscroll-contain px-3 py-3 sm:space-y-4 sm:px-4 sm:py-4'
className={sideDrawerFormClassName('gap-5')}
>
<ApiKeyFormSection
title={t('Basic Information')}
description={t('Set API key basic information')}
icon={KeyRound}
>
<SideDrawerSection>
<SideDrawerSectionHeader
title={t('Basic Information')}
description={t('Set API key basic information')}
icon={<KeyRound className='size-4' />}
/>
<FormField
control={form.control}
name='name'
@@ -347,8 +319,8 @@ export function ApiKeysMutateDrawer({
control={form.control}
name='cross_group_retry'
render={({ field }) => (
<FormItem className='flex min-h-16 flex-row items-center justify-between gap-3 rounded-lg border px-3 py-2.5 sm:min-h-20 sm:gap-4 sm:px-4 sm:py-3'>
<div className='space-y-0.5'>
<FormItem className={sideDrawerSwitchItemClassName()}>
<div className='flex flex-col gap-0.5'>
<FormLabel className='text-sm'>
{t('Cross-group retry')}
</FormLabel>
@@ -456,13 +428,14 @@ export function ApiKeysMutateDrawer({
)}
/>
)}
</ApiKeyFormSection>
</SideDrawerSection>
<ApiKeyFormSection
title={t('Quota Settings')}
description={t('Set quota amount and limits')}
icon={WalletCards}
>
<SideDrawerSection>
<SideDrawerSectionHeader
title={t('Quota Settings')}
description={t('Set quota amount and limits')}
icon={<WalletCards className='size-4' />}
/>
{!unlimitedQuota && (
<FormField
control={form.control}
@@ -498,8 +471,8 @@ export function ApiKeysMutateDrawer({
control={form.control}
name='unlimited_quota'
render={({ field }) => (
<FormItem className='flex min-h-16 flex-row items-center justify-between gap-3 rounded-lg border px-3 py-2.5 sm:min-h-20 sm:gap-4 sm:px-4 sm:py-3'>
<div className='space-y-0.5'>
<FormItem className={sideDrawerSwitchItemClassName()}>
<div className='flex flex-col gap-0.5'>
<FormLabel className='text-sm'>
{t('Unlimited Quota')}
</FormLabel>
@@ -516,29 +489,24 @@ export function ApiKeysMutateDrawer({
</FormItem>
)}
/>
</ApiKeyFormSection>
</SideDrawerSection>
<Collapsible open={advancedOpen} onOpenChange={setAdvancedOpen}>
<section className='bg-card rounded-lg border'>
<SideDrawerSection>
<CollapsibleTrigger
render={
<button
type='button'
className='hover:bg-muted/50 flex w-full items-center gap-2.5 px-3 py-2.5 text-left transition-colors sm:gap-3 sm:px-4 sm:py-3'
className='hover:bg-muted/40 flex w-full items-center gap-3 rounded-md py-1.5 text-left transition-colors'
/>
}
>
<div className='bg-muted text-muted-foreground flex size-8 shrink-0 items-center justify-center rounded-lg border sm:size-10'>
<Settings2 className='size-4 sm:size-5' />
</div>
<div className='min-w-0 flex-1'>
<h3 className='text-sm leading-none font-medium'>
{t('Advanced Settings')}
</h3>
<p className='text-muted-foreground mt-1 text-xs'>
{t('Set API key access restrictions')}
</p>
</div>
<SideDrawerSectionHeader
className='flex-1'
title={t('Advanced Settings')}
description={t('Set API key access restrictions')}
icon={<Settings2 className='size-4' />}
/>
<ChevronDown
className={cn(
'text-muted-foreground size-4 shrink-0 transition-transform',
@@ -547,7 +515,7 @@ export function ApiKeysMutateDrawer({
/>
</CollapsibleTrigger>
<CollapsibleContent>
<div className='space-y-3 border-t p-3 sm:space-y-4 sm:p-4'>
<div className='flex flex-col gap-4 pt-2'>
<FormField
control={form.control}
name='model_limits'
@@ -604,11 +572,11 @@ export function ApiKeysMutateDrawer({
/>
</div>
</CollapsibleContent>
</section>
</SideDrawerSection>
</Collapsible>
</form>
</Form>
<SheetFooter className='bg-background grid grid-cols-2 gap-2 border-t px-3 py-3 sm:flex sm:flex-row sm:justify-end sm:px-5 sm:py-4'>
<SheetFooter className={sideDrawerFooterClassName()}>
<SheetClose
render={<Button variant='outline' className='w-full sm:w-auto' />}
>