feat(ui): improve mobile responsive layouts

This commit is contained in:
CaIon
2026-04-30 19:53:02 +08:00
parent aa730395f1
commit d46df94f05
84 changed files with 1174 additions and 731 deletions
@@ -769,7 +769,7 @@ export function useCommonLogsColumns(isAdmin: boolean): ColumnDef<UsageLog>[] {
</>
)
},
meta: { label: t('Details'), mobileHidden: true },
meta: { label: t('Details') },
size: 180,
maxSize: 200,
}
@@ -141,9 +141,9 @@ export function CommonLogsFilterBar({
!!filters.requestId
return (
<div className='space-y-3'>
<div className='space-y-2 sm: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]'>
<div className='grid grid-cols-2 gap-1.5 sm:grid-cols-4 sm:gap-2 lg:grid-cols-[minmax(280px,2fr)_minmax(140px,1fr)_minmax(120px,1fr)_minmax(120px,0.8fr)_auto]'>
<CompactDateTimeRangePicker
start={filters.startTime}
end={filters.endTime}
@@ -214,7 +214,7 @@ export function CommonLogsFilterBar({
)}
>
<div className='min-h-0 overflow-hidden'>
<div className='grid grid-cols-2 gap-2 sm:grid-cols-4'>
<div className='grid grid-cols-2 gap-1.5 sm:grid-cols-4 sm:gap-2'>
<Input
placeholder={t('Token Name')}
type={sensitiveVisible ? 'text' : 'password'}
@@ -257,9 +257,12 @@ export function CommonLogsFilterBar({
<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>}
</div>
<div className='flex shrink-0 items-center gap-2 self-end sm:self-auto'>
<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'
className='text-muted-foreground hover:text-foreground inline-flex size-8 items-center justify-center rounded-md border transition-colors'
title={sensitiveVisible ? t('Hide') : t('Show')}
aria-label={sensitiveVisible ? t('Hide') : t('Show')}
onClick={() => setSensitiveVisible(!sensitiveVisible)}
@@ -270,9 +273,6 @@ export function CommonLogsFilterBar({
<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'
@@ -63,13 +63,13 @@ function DetailRow(props: {
muted?: boolean
}) {
return (
<div className='flex items-start gap-3 text-sm'>
<span className='text-muted-foreground w-28 shrink-0 text-xs'>
<div className='grid min-w-0 grid-cols-[5.25rem_minmax(0,1fr)] gap-2 text-sm sm:grid-cols-[7rem_minmax(0,1fr)] sm:gap-3'>
<span className='text-muted-foreground min-w-0 text-xs'>
{props.label}
</span>
<span
className={cn(
'min-w-0 text-xs break-words',
'min-w-0 max-w-full text-xs break-all sm:break-words',
props.mono && 'font-mono',
props.muted && 'text-muted-foreground'
)}
@@ -88,7 +88,7 @@ function DetailSection(props: {
}) {
const isDanger = props.variant === 'danger'
return (
<div className='space-y-1.5'>
<div className='min-w-0 space-y-1.5'>
<Label
className={cn(
'flex items-center gap-1.5 text-xs font-semibold',
@@ -100,7 +100,7 @@ function DetailSection(props: {
</Label>
<div
className={cn(
'space-y-1.5 rounded-md border p-2.5',
'min-w-0 space-y-1 overflow-hidden rounded-md border p-2.5 max-sm:p-2',
isDanger
? 'border-red-200 bg-red-50 dark:border-red-900 dark:bg-red-950/20'
: 'bg-muted/30'
@@ -462,11 +462,12 @@ export function DetailsDialog(props: DetailsDialogProps) {
<Dialog open={props.open} onOpenChange={props.onOpenChange}>
<DialogContent
className={cn(
'min-w-0',
'min-w-0 overflow-hidden',
'max-sm:max-h-[calc(100dvh-1.5rem)] max-sm:w-[calc(100vw-1.5rem)] max-sm:max-w-[calc(100vw-1.5rem)] max-sm:p-4',
isTieredBilling ? 'sm:max-w-4xl lg:max-w-5xl' : 'sm:max-w-lg'
)}
>
<DialogHeader>
<DialogHeader className='max-sm:gap-1'>
<DialogTitle className='flex items-center gap-2 text-base'>
{t('Log Details')}
<StatusBadge
@@ -481,10 +482,10 @@ export function DetailsDialog(props: DetailsDialogProps) {
</DialogDescription>
</DialogHeader>
<ScrollArea className='max-h-[70vh] min-w-0 pr-4'>
<div className='min-w-0 space-y-3 py-1'>
<ScrollArea className='max-h-[70vh] min-w-0 overflow-hidden pr-2 max-sm:max-h-[calc(100dvh-7rem)] sm:pr-4'>
<div className='w-full min-w-0 max-w-full space-y-2.5 overflow-hidden py-1 sm:space-y-3'>
{/* Overview section - key identifiers */}
<div className='space-y-1.5'>
<div className='min-w-0 space-y-1'>
{props.log.request_id && (
<DetailRow
label={t('Request ID')}
@@ -587,7 +588,7 @@ export function DetailsDialog(props: DetailsDialogProps) {
{/* Request conversion (admin only, not for refund) */}
{showConversion && (
<DetailSection label={t('Request Conversion')}>
<div className='relative'>
<div className='relative min-w-0'>
<Button
variant='ghost'
size='sm'
@@ -602,7 +603,7 @@ export function DetailsDialog(props: DetailsDialogProps) {
<Copy className='size-3' />
)}
</Button>
<div className='space-y-1 pr-6'>
<div className='min-w-0 space-y-1 pr-6'>
{other?.request_path && (
<DetailRow
label={t('Path')}
@@ -610,12 +611,14 @@ export function DetailsDialog(props: DetailsDialogProps) {
mono
/>
)}
<div className='flex items-center gap-1.5 text-xs'>
<div className='flex min-w-0 items-center gap-1.5 text-xs'>
<Route
className='text-muted-foreground size-3'
aria-hidden='true'
/>
<span className='break-words'>{conversionLabel}</span>
<span className='min-w-0 break-all sm:break-words'>
{conversionLabel}
</span>
</div>
</div>
</div>
@@ -825,7 +828,7 @@ export function DetailsDialog(props: DetailsDialogProps) {
{/* Tiered pricing breakdown (when billing_mode is tiered_expr) */}
{isTieredBilling && other?.expr_b64 && (
<div className='bg-muted/30 min-w-0 rounded-md border px-3'>
<div className='bg-muted/30 min-w-0 overflow-hidden rounded-md border px-3 max-sm:px-2'>
<DynamicPricingBreakdown
billingExpr={decodeBillingExprB64(other.expr_b64)}
matchedTierLabel={other.matched_tier}
@@ -964,7 +967,7 @@ export function DetailsDialog(props: DetailsDialogProps) {
return (
<div
key={idx}
className='bg-background/60 flex items-start gap-2 rounded border p-2'
className='bg-background/60 flex min-w-0 flex-col gap-1.5 rounded border p-2 sm:flex-row sm:items-start sm:gap-2'
>
<StatusBadge
variant='neutral'
@@ -972,7 +975,7 @@ export function DetailsDialog(props: DetailsDialogProps) {
className='shrink-0 font-medium'
copyable={false}
/>
<span className='min-w-0 font-mono text-[11px] leading-relaxed break-words'>
<span className='min-w-0 font-mono text-[11px] leading-relaxed break-all sm:break-words'>
{parsed.content}
</span>
</div>
@@ -985,7 +988,7 @@ export function DetailsDialog(props: DetailsDialogProps) {
{details && (
<div className='space-y-1.5'>
<Label className='text-xs font-semibold'>{t('Content')}</Label>
<div className='bg-muted/30 relative rounded-md border p-2.5'>
<div className='bg-muted/30 relative min-w-0 overflow-hidden rounded-md border p-2.5'>
<Button
variant='ghost'
size='sm'
@@ -1000,7 +1003,7 @@ export function DetailsDialog(props: DetailsDialogProps) {
<Copy className='size-3' />
)}
</Button>
<p className='pr-6 text-xs leading-relaxed break-words whitespace-pre-wrap'>
<p className='min-w-0 pr-6 text-xs leading-relaxed break-all whitespace-pre-wrap sm:break-words'>
{details}
</p>
</div>
@@ -271,7 +271,7 @@ export function UsageLogsFilterDialog({
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogContent className='sm:max-w-lg'>
<DialogContent className='flex max-h-[calc(100dvh-2rem)] flex-col max-sm:h-dvh max-sm:w-screen max-sm:max-w-none max-sm:rounded-none max-sm:p-4 sm:max-w-lg'>
<DialogHeader>
<DialogTitle>
{t('Filter')} {t(getLogCategoryLabel(logCategory))} {t('Logs')}
@@ -281,15 +281,15 @@ export function UsageLogsFilterDialog({
</DialogDescription>
</DialogHeader>
<ScrollArea className='max-h-[60vh] pr-4'>
<div className='grid gap-4 py-4'>
<ScrollArea className='min-h-0 flex-1 pr-3 sm:max-h-[60vh] sm:pr-4'>
<div className='grid gap-3 py-3 sm:gap-4 sm:py-4'>
{/* Quick time range selection */}
<div className='grid gap-2'>
<Label className='flex items-center gap-2'>
<Calendar className='h-4 w-4' />
{t('Quick Range')}
</Label>
<div className='flex gap-2'>
<div className='grid grid-cols-2 gap-2 sm:flex'>
{TIME_RANGE_PRESETS.map((range) => (
<Button
key={range.days}
@@ -314,7 +314,7 @@ export function UsageLogsFilterDialog({
<SectionDivider label={t('Custom Time Range')} />
{/* Custom time range */}
<div className='grid gap-4'>
<div className='grid gap-3 sm:gap-4'>
<div className='grid gap-2'>
<Label htmlFor='start_time'>{t('Start Time')}</Label>
<DateTimePicker
@@ -355,7 +355,7 @@ export function UsageLogsFilterDialog({
</div>
</ScrollArea>
<DialogFooter>
<DialogFooter className='grid grid-cols-2 gap-2 sm:flex'>
<Button onClick={handleReset} variant='outline' type='button'>
<RotateCcw className='mr-2 h-4 w-4' />
{t('Reset')}
@@ -138,8 +138,8 @@ export function TaskLogsFilterBar(props: TaskLogsFilterBarProps) {
)
return (
<div className='space-y-3'>
<div className='grid grid-cols-2 gap-2 lg:grid-cols-[minmax(280px,2fr)_minmax(180px,1fr)_minmax(120px,0.8fr)_auto]'>
<div className='space-y-2 sm:space-y-3'>
<div className='grid grid-cols-2 gap-1.5 sm:gap-2 lg:grid-cols-[minmax(280px,2fr)_minmax(180px,1fr)_minmax(120px,0.8fr)_auto]'>
<CompactDateTimeRangePicker
start={filters.startTime}
end={filters.endTime}
@@ -166,7 +166,7 @@ export function TaskLogsFilterBar(props: TaskLogsFilterBarProps) {
className='h-9'
/>
)}
<div className='col-span-2 flex shrink-0 items-center justify-end gap-2 lg:col-span-1'>
<div className='col-span-2 flex shrink-0 items-center justify-end gap-1.5 sm:gap-2 lg:col-span-1'>
<Button
variant='outline'
size='sm'
@@ -67,7 +67,7 @@ export function UsageLogsTable({ logCategory }: UsageLogsTableProps) {
} = useTableUrlState({
search: route.useSearch(),
navigate: route.useNavigate(),
pagination: { defaultPage: 1, defaultPageSize: 100 },
pagination: { defaultPage: 1, defaultPageSize: isMobile ? 20 : 100 },
globalFilter: { enabled: false },
columnFilters: [
{ columnId: 'created_at', searchKey: 'type', type: 'array' as const },
@@ -185,16 +185,16 @@ export function UsageLogsTable({ logCategory }: UsageLogsTableProps) {
return (
<>
<div className='space-y-4'>
<div className='space-y-3 sm:space-y-4'>
{logCategory === 'common' ? (
<div className='rounded-md border bg-card/50 p-3 shadow-xs'>
<div className='rounded-md border bg-card/50 p-2 shadow-xs sm:p-3'>
<CommonLogsFilterBar
stats={<CommonLogsStats />}
viewOptions={<DataTableViewOptions table={table} />}
/>
</div>
) : (
<div className='rounded-md border bg-card/50 p-3 shadow-xs'>
<div className='rounded-md border bg-card/50 p-2 shadow-xs sm:p-3'>
<TaskLogsFilterBar
logCategory={logCategory}
viewOptions={<DataTableViewOptions table={table} />}