feat(ui): improve mobile responsive layouts
This commit is contained in:
+1
-1
@@ -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'
|
||||
|
||||
+22
-19
@@ -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>
|
||||
|
||||
+6
-6
@@ -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} />}
|
||||
|
||||
Reference in New Issue
Block a user