mirror of
https://github.com/QuantumNous/new-api.git
synced 2026-09-07 01:56:53 +00:00
feat(ui): improve mobile responsive layouts
This commit is contained in:
+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')}
|
||||
|
||||
Reference in New Issue
Block a user