✨ 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:
@@ -23,6 +23,7 @@ import { formatTimestampToDate } from '@/lib/format'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { DataTableColumnHeader } from '@/components/data-table/column-header'
|
||||
import { StatusBadge } from '@/components/status-badge'
|
||||
import { TableId } from '@/components/table-id'
|
||||
import { getDeploymentStatusConfig } from '../constants'
|
||||
import {
|
||||
formatRemainingMinutes,
|
||||
@@ -50,15 +51,7 @@ export function useDeploymentsColumns(opts: {
|
||||
),
|
||||
cell: ({ row }) => {
|
||||
const id = row.original.id
|
||||
return (
|
||||
<StatusBadge
|
||||
label={String(id)}
|
||||
variant='neutral'
|
||||
copyText={String(id)}
|
||||
size='sm'
|
||||
className='font-mono'
|
||||
/>
|
||||
)
|
||||
return <TableId value={id} />
|
||||
},
|
||||
size: 120,
|
||||
},
|
||||
@@ -100,7 +93,6 @@ export function useDeploymentsColumns(opts: {
|
||||
<StatusBadge
|
||||
label={config.label}
|
||||
variant={config.variant}
|
||||
showDot={config.showDot}
|
||||
size='sm'
|
||||
copyable={false}
|
||||
/>
|
||||
|
||||
+20
-13
@@ -51,6 +51,13 @@ import {
|
||||
SheetTitle,
|
||||
} from '@/components/ui/sheet'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import {
|
||||
SideDrawerSection,
|
||||
sideDrawerContentClassName,
|
||||
sideDrawerFooterClassName,
|
||||
sideDrawerFormClassName,
|
||||
sideDrawerHeaderClassName,
|
||||
} from '@/components/drawer-layout'
|
||||
import { MultiSelect } from '@/components/multi-select'
|
||||
import {
|
||||
checkClusterNameAvailability,
|
||||
@@ -375,8 +382,8 @@ export function CreateDeploymentDrawer({
|
||||
}
|
||||
}}
|
||||
>
|
||||
<SheetContent className='flex w-full flex-col sm:max-w-[600px]'>
|
||||
<SheetHeader className='text-start'>
|
||||
<SheetContent className={sideDrawerContentClassName('sm:max-w-[600px]')}>
|
||||
<SheetHeader className={sideDrawerHeaderClassName()}>
|
||||
<SheetTitle>{t('Create deployment')}</SheetTitle>
|
||||
<SheetDescription>
|
||||
{t('Configure and deploy a new container instance.')}
|
||||
@@ -389,10 +396,10 @@ export function CreateDeploymentDrawer({
|
||||
onSubmit={form.handleSubmit((values) =>
|
||||
createMutation.mutate(values)
|
||||
)}
|
||||
className='flex-1 space-y-6 overflow-y-auto px-4'
|
||||
className={sideDrawerFormClassName()}
|
||||
>
|
||||
{/* Basic Configuration */}
|
||||
<div className='space-y-4'>
|
||||
<SideDrawerSection>
|
||||
<h3 className='text-sm font-medium'>
|
||||
{t('Basic Configuration')}
|
||||
</h3>
|
||||
@@ -435,10 +442,10 @@ export function CreateDeploymentDrawer({
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</SideDrawerSection>
|
||||
|
||||
{/* Resource Configuration */}
|
||||
<div className='space-y-4'>
|
||||
<SideDrawerSection>
|
||||
<h3 className='text-sm font-medium'>
|
||||
{t('Resource Configuration')}
|
||||
</h3>
|
||||
@@ -604,10 +611,10 @@ export function CreateDeploymentDrawer({
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</SideDrawerSection>
|
||||
|
||||
{/* Price Estimation */}
|
||||
<div className='space-y-4'>
|
||||
<SideDrawerSection>
|
||||
<h3 className='text-sm font-medium'>{t('Price estimation')}</h3>
|
||||
<p className='text-muted-foreground text-xs'>
|
||||
{t('Price estimation description')}
|
||||
@@ -642,10 +649,10 @@ export function CreateDeploymentDrawer({
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</SideDrawerSection>
|
||||
|
||||
{/* Advanced Configuration */}
|
||||
<div className='space-y-4'>
|
||||
<SideDrawerSection>
|
||||
<h3 className='text-sm font-medium'>
|
||||
{t('Advanced Configuration')}
|
||||
</h3>
|
||||
@@ -653,7 +660,7 @@ export function CreateDeploymentDrawer({
|
||||
{t('Optional settings for advanced container configuration.')}
|
||||
</p>
|
||||
|
||||
<div className='space-y-4'>
|
||||
<div className='flex flex-col gap-4'>
|
||||
<div className='grid gap-4 sm:grid-cols-2'>
|
||||
<FormField
|
||||
control={form.control}
|
||||
@@ -758,11 +765,11 @@ export function CreateDeploymentDrawer({
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</SideDrawerSection>
|
||||
</form>
|
||||
</Form>
|
||||
|
||||
<SheetFooter className='gap-2'>
|
||||
<SheetFooter className={sideDrawerFooterClassName()}>
|
||||
<SheetClose render={<Button variant='outline' />}>
|
||||
{t('Cancel')}
|
||||
</SheetClose>
|
||||
|
||||
+2
-7
@@ -65,6 +65,7 @@ import {
|
||||
} from '@/components/ui/table'
|
||||
import { ConfirmDialog } from '@/components/confirm-dialog'
|
||||
import { StatusBadge } from '@/components/status-badge'
|
||||
import { TableId } from '@/components/table-id'
|
||||
import { deletePrefillGroup, getPrefillGroups } from '../../api'
|
||||
import { prefillGroupsQueryKeys } from '../../lib'
|
||||
import type { PrefillGroup } from '../../types'
|
||||
@@ -405,13 +406,7 @@ export function PrefillGroupManagementDialog({
|
||||
<span className='font-medium'>
|
||||
{group.name}
|
||||
</span>
|
||||
<StatusBadge
|
||||
label={`#${group.id}`}
|
||||
variant='neutral'
|
||||
size='sm'
|
||||
copyable={false}
|
||||
className='font-mono'
|
||||
/>
|
||||
<TableId value={group.id} />
|
||||
</div>
|
||||
{group.description ? (
|
||||
<p className='text-muted-foreground text-xs'>
|
||||
|
||||
+27
-28
@@ -50,7 +50,6 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import {
|
||||
Sheet,
|
||||
SheetClose,
|
||||
@@ -62,6 +61,14 @@ import {
|
||||
} from '@/components/ui/sheet'
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import {
|
||||
SideDrawerSection,
|
||||
sideDrawerContentClassName,
|
||||
sideDrawerFooterClassName,
|
||||
sideDrawerFormClassName,
|
||||
sideDrawerHeaderClassName,
|
||||
sideDrawerSwitchItemClassName,
|
||||
} from '@/components/drawer-layout'
|
||||
import { JsonEditor } from '@/components/json-editor'
|
||||
import { TagInput } from '@/components/tag-input'
|
||||
import {
|
||||
@@ -627,8 +634,8 @@ export function ModelMutateDrawer({
|
||||
|
||||
return (
|
||||
<Sheet open={open} onOpenChange={onOpenChange}>
|
||||
<SheetContent className='flex h-dvh w-full flex-col gap-0 overflow-hidden p-0 sm:max-w-2xl'>
|
||||
<SheetHeader className='border-b px-4 py-3 text-start sm:px-6 sm:py-4'>
|
||||
<SheetContent className={sideDrawerContentClassName('sm:max-w-2xl')}>
|
||||
<SheetHeader className={sideDrawerHeaderClassName()}>
|
||||
<SheetTitle>
|
||||
{isEditing ? t('Edit Model') : t('Create Model')}
|
||||
</SheetTitle>
|
||||
@@ -647,10 +654,10 @@ export function ModelMutateDrawer({
|
||||
onSubmit={form.handleSubmit(
|
||||
onSubmit as Parameters<typeof form.handleSubmit>[0]
|
||||
)}
|
||||
className='flex-1 space-y-4 overflow-y-auto px-3 py-3 pb-4 sm:space-y-6 sm:px-4'
|
||||
className={sideDrawerFormClassName()}
|
||||
>
|
||||
{/* Basic Information */}
|
||||
<div className='space-y-4'>
|
||||
<SideDrawerSection>
|
||||
<h3 className='text-sm font-semibold'>
|
||||
{t('Basic Information')}
|
||||
</h3>
|
||||
@@ -774,12 +781,10 @@ export function ModelMutateDrawer({
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
</SideDrawerSection>
|
||||
|
||||
{/* Matching Configuration */}
|
||||
<div className='space-y-4'>
|
||||
<SideDrawerSection>
|
||||
<h3 className='text-sm font-semibold'>{t('Matching Rules')}</h3>
|
||||
|
||||
<FormField
|
||||
@@ -822,12 +827,10 @@ export function ModelMutateDrawer({
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
</SideDrawerSection>
|
||||
|
||||
{/* Endpoints Configuration */}
|
||||
<div className='space-y-4'>
|
||||
<SideDrawerSection>
|
||||
<div className='flex items-center justify-between'>
|
||||
<h3 className='text-sm font-semibold'>{t('Endpoints')}</h3>
|
||||
<Select<string>
|
||||
@@ -883,12 +886,10 @@ export function ModelMutateDrawer({
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
</SideDrawerSection>
|
||||
|
||||
{/* Pricing Configuration */}
|
||||
<div className='space-y-4'>
|
||||
<SideDrawerSection>
|
||||
<h3 className='text-sm font-semibold'>
|
||||
{t('Pricing Configuration')}
|
||||
</h3>
|
||||
@@ -1114,7 +1115,7 @@ export function ModelMutateDrawer({
|
||||
}`}
|
||||
/>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent className='space-y-6 pt-6'>
|
||||
<CollapsibleContent className='flex flex-col gap-4 pt-4'>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='cacheRatio'
|
||||
@@ -1226,20 +1227,18 @@ export function ModelMutateDrawer({
|
||||
</Collapsible>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
</SideDrawerSection>
|
||||
|
||||
{/* Status & Sync */}
|
||||
<div className='space-y-4'>
|
||||
<SideDrawerSection>
|
||||
<h3 className='text-sm font-semibold'>{t('Status & Sync')}</h3>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='status'
|
||||
render={({ field }) => (
|
||||
<FormItem className='flex items-center justify-between rounded-lg border p-4'>
|
||||
<div className='space-y-0.5'>
|
||||
<FormItem className={sideDrawerSwitchItemClassName()}>
|
||||
<div className='flex flex-col gap-0.5'>
|
||||
<FormLabel className='text-base'>
|
||||
{t('Enabled')}
|
||||
</FormLabel>
|
||||
@@ -1261,8 +1260,8 @@ export function ModelMutateDrawer({
|
||||
control={form.control}
|
||||
name='sync_official'
|
||||
render={({ field }) => (
|
||||
<FormItem className='flex items-center justify-between rounded-lg border p-4'>
|
||||
<div className='space-y-0.5'>
|
||||
<FormItem className={sideDrawerSwitchItemClassName()}>
|
||||
<div className='flex flex-col gap-0.5'>
|
||||
<FormLabel className='text-base'>
|
||||
{t('Official Sync')}
|
||||
</FormLabel>
|
||||
@@ -1279,11 +1278,11 @@ export function ModelMutateDrawer({
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</SideDrawerSection>
|
||||
</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} />}
|
||||
>
|
||||
|
||||
+18
-14
@@ -42,7 +42,6 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import {
|
||||
Sheet,
|
||||
SheetClose,
|
||||
@@ -53,6 +52,13 @@ import {
|
||||
SheetTitle,
|
||||
} from '@/components/ui/sheet'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import {
|
||||
SideDrawerSection,
|
||||
sideDrawerContentClassName,
|
||||
sideDrawerFooterClassName,
|
||||
sideDrawerFormClassName,
|
||||
sideDrawerHeaderClassName,
|
||||
} from '@/components/drawer-layout'
|
||||
import { JsonEditor } from '@/components/json-editor'
|
||||
import { StatusBadge } from '@/components/status-badge'
|
||||
import { TagInput } from '@/components/tag-input'
|
||||
@@ -180,8 +186,8 @@ export function PrefillGroupFormDrawer({
|
||||
|
||||
return (
|
||||
<Sheet open={open} onOpenChange={handleOpenChange}>
|
||||
<SheetContent className='flex h-dvh w-full flex-col gap-0 overflow-hidden p-0 sm:max-w-2xl'>
|
||||
<SheetHeader className='border-b px-4 py-3 text-start sm:px-6 sm:py-4'>
|
||||
<SheetContent className={sideDrawerContentClassName('sm:max-w-2xl')}>
|
||||
<SheetHeader className={sideDrawerHeaderClassName()}>
|
||||
<SheetTitle>
|
||||
{isEdit ? t('Edit Prefill Group') : t('Create Prefill Group')}
|
||||
</SheetTitle>
|
||||
@@ -196,10 +202,10 @@ export function PrefillGroupFormDrawer({
|
||||
<form
|
||||
id='prefill-group-form'
|
||||
onSubmit={form.handleSubmit(handleSubmit)}
|
||||
className='flex-1 space-y-4 overflow-y-auto px-3 py-3 pb-4 sm:space-y-6 sm:px-4'
|
||||
className={sideDrawerFormClassName()}
|
||||
>
|
||||
<div className='space-y-4'>
|
||||
<div className='space-y-1'>
|
||||
<SideDrawerSection>
|
||||
<div className='flex flex-col gap-1'>
|
||||
<h3 className='text-sm font-semibold'>{t('Group details')}</h3>
|
||||
<p className='text-muted-foreground text-sm'>
|
||||
{t(
|
||||
@@ -252,12 +258,10 @@ export function PrefillGroupFormDrawer({
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</SideDrawerSection>
|
||||
|
||||
<Separator />
|
||||
|
||||
<div className='space-y-4'>
|
||||
<div className='space-y-1'>
|
||||
<SideDrawerSection>
|
||||
<div className='flex flex-col gap-1'>
|
||||
<h3 className='text-sm font-semibold'>{t('Configuration')}</h3>
|
||||
<p className='text-muted-foreground text-sm'>
|
||||
{t('Choose the bundle type and define the items inside it.')}
|
||||
@@ -326,7 +330,7 @@ export function PrefillGroupFormDrawer({
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className='space-y-2 rounded-lg border p-3 sm:p-4'>
|
||||
<div className='border-border/60 flex flex-col gap-3 border-y py-4'>
|
||||
<div className='flex items-center gap-2'>
|
||||
<h4 className='text-sm font-medium'>{t('Project')}</h4>
|
||||
<StatusBadge
|
||||
@@ -379,11 +383,11 @@ export function PrefillGroupFormDrawer({
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</SideDrawerSection>
|
||||
</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 type='button' variant='outline' disabled={isSaving} />
|
||||
|
||||
@@ -29,7 +29,8 @@ import {
|
||||
} from '@/components/ui/tooltip'
|
||||
import { DataTableColumnHeader } from '@/components/data-table/column-header'
|
||||
import { GroupBadge } from '@/components/group-badge'
|
||||
import { StatusBadge } from '@/components/status-badge'
|
||||
import { StatusBadge, StatusBadgeList } from '@/components/status-badge'
|
||||
import { TableId } from '@/components/table-id'
|
||||
import {
|
||||
getModelStatusConfig,
|
||||
getNameRuleConfig,
|
||||
@@ -47,25 +48,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-x-auto'>
|
||||
{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}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -118,15 +106,7 @@ export function useModelsColumns(vendors: Vendor[] = []): ColumnDef<Model>[] {
|
||||
),
|
||||
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,
|
||||
},
|
||||
@@ -250,7 +230,6 @@ export function useModelsColumns(vendors: Vendor[] = []): ColumnDef<Model>[] {
|
||||
<StatusBadge
|
||||
label={config.label}
|
||||
variant={config.variant}
|
||||
showDot={config.showDot}
|
||||
size='sm'
|
||||
copyable={false}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user