perf(web): improve dialog sizing and footer layout

- migrate frontend dialogs to the shared footer API so actions stay separated from scrollable body content.
- tune dialog dimensions for model analytics, prefill groups, billing history, channel model sync, and related workflows.
- update channel terminology and dialog action translations across supported locales.
This commit is contained in:
QuentinHsu
2026-06-06 21:49:33 +08:00
parent 7a5348caa3
commit 2eaa943d9f
80 changed files with 8137 additions and 8517 deletions
@@ -21,15 +21,6 @@ import { Save, Settings2 } from 'lucide-react'
import { useTranslation } from 'react-i18next'
import type { TimeGranularity } from '@/lib/time'
import { Button } from '@/components/ui/button'
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from '@/components/ui/dialog'
import { Label } from '@/components/ui/label'
import {
Select,
@@ -39,6 +30,7 @@ import {
SelectTrigger,
SelectValue,
} from '@/components/ui/select'
import { Dialog } from '@/components/dialog'
import {
CONSUMPTION_DISTRIBUTION_CHART_OPTIONS,
MODEL_ANALYTICS_CHART_OPTIONS,
@@ -74,165 +66,158 @@ export function ModelsChartPreferences(props: ModelsChartPreferencesProps) {
}
return (
<Dialog open={open} onOpenChange={handleOpenChange}>
<DialogTrigger render={<Button variant='outline' size='sm' />}>
<Settings2 className='mr-2 h-4 w-4' />
{t('Preferences')}
</DialogTrigger>
<DialogContent className='sm:max-w-md'>
<DialogHeader>
<DialogTitle>{t('Dashboard Preferences')}</DialogTitle>
<DialogDescription>
{t(
'Choose the default charts, range, and time granularity for model analytics.'
)}
</DialogDescription>
</DialogHeader>
<div className='grid gap-4 py-2'>
<div className='grid gap-2'>
<Label htmlFor='default-time-range'>{t('Default range')}</Label>
<Select
items={[
...TIME_RANGE_PRESETS.map((option) => ({
value: String(option.days),
label: t(option.label),
})),
]}
value={String(draft.defaultTimeRangeDays)}
onValueChange={(value) =>
setDraft((prev) => ({
...prev,
defaultTimeRangeDays: Number(value),
}))
}
>
<SelectTrigger id='default-time-range'>
<SelectValue placeholder={t('Select default range')} />
</SelectTrigger>
<SelectContent alignItemWithTrigger={false}>
<SelectGroup>
{TIME_RANGE_PRESETS.map((option) => (
<SelectItem key={option.days} value={String(option.days)}>
{t(option.label)}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</div>
<div className='grid gap-2'>
<Label htmlFor='default-time-granularity'>
{t('Default time granularity')}
</Label>
<Select
items={[
...TIME_GRANULARITY_OPTIONS.map((option) => ({
value: option.value,
label: t(option.label),
})),
]}
value={draft.defaultTimeGranularity}
onValueChange={(value) =>
setDraft((prev) => ({
...prev,
defaultTimeGranularity: value as TimeGranularity,
}))
}
>
<SelectTrigger id='default-time-granularity'>
<SelectValue placeholder={t('Select time granularity')} />
</SelectTrigger>
<SelectContent alignItemWithTrigger={false}>
<SelectGroup>
{TIME_GRANULARITY_OPTIONS.map((option) => (
<SelectItem key={option.value} value={option.value}>
{t(option.label)}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</div>
<div className='grid gap-2'>
<Label htmlFor='consumption-distribution-chart'>
{t('Default consumption chart')}
</Label>
<Select
items={[
...CONSUMPTION_DISTRIBUTION_CHART_OPTIONS.map((option) => ({
value: option.value,
label: t(option.labelKey),
})),
]}
value={draft.consumptionDistributionChart}
onValueChange={(value) =>
setDraft((prev) => ({
...prev,
consumptionDistributionChart:
value as ConsumptionDistributionChartType,
}))
}
>
<SelectTrigger id='consumption-distribution-chart'>
<SelectValue placeholder={t('Select default chart')} />
</SelectTrigger>
<SelectContent alignItemWithTrigger={false}>
<SelectGroup>
{CONSUMPTION_DISTRIBUTION_CHART_OPTIONS.map((option) => (
<SelectItem key={option.value} value={option.value}>
{t(option.labelKey)}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</div>
<div className='grid gap-2'>
<Label htmlFor='model-analytics-chart'>
{t('Default model call chart')}
</Label>
<Select
items={[
...MODEL_ANALYTICS_CHART_OPTIONS.map((option) => ({
value: option.value,
label: t(option.labelKey),
})),
]}
value={draft.modelAnalyticsChart}
onValueChange={(value) =>
setDraft((prev) => ({
...prev,
modelAnalyticsChart: value as ModelAnalyticsChartTab,
}))
}
>
<SelectTrigger id='model-analytics-chart'>
<SelectValue placeholder={t('Select default chart')} />
</SelectTrigger>
<SelectContent alignItemWithTrigger={false}>
<SelectGroup>
{MODEL_ANALYTICS_CHART_OPTIONS.map((option) => (
<SelectItem key={option.value} value={option.value}>
{t(option.labelKey)}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</div>
</div>
<DialogFooter>
<Button onClick={handleSave} type='button'>
<Save className='mr-2 h-4 w-4' />
{t('Save Preferences')}
</Button>
</DialogFooter>
</DialogContent>
<Dialog
open={open}
onOpenChange={handleOpenChange}
trigger={
<Button variant='outline' size='sm'>
<Settings2 className='mr-2 h-4 w-4' />
{t('Preferences')}
</Button>
}
title={t('Model Analytics Defaults')}
description={t('Set default ranges and charts for model analytics.')}
contentClassName='sm:max-w-md'
contentHeight='auto'
bodyClassName='grid gap-3'
footer={
<Button onClick={handleSave} type='button'>
<Save className='mr-2 h-4 w-4' />
{t('Save Preferences')}
</Button>
}
>
<div className='grid gap-1.5'>
<Label htmlFor='default-time-range'>{t('Default range')}</Label>
<Select
items={[
...TIME_RANGE_PRESETS.map((option) => ({
value: String(option.days),
label: t(option.label),
})),
]}
value={String(draft.defaultTimeRangeDays)}
onValueChange={(value) =>
setDraft((prev) => ({
...prev,
defaultTimeRangeDays: Number(value),
}))
}
>
<SelectTrigger id='default-time-range'>
<SelectValue placeholder={t('Select default range')} />
</SelectTrigger>
<SelectContent alignItemWithTrigger={false}>
<SelectGroup>
{TIME_RANGE_PRESETS.map((option) => (
<SelectItem key={option.days} value={String(option.days)}>
{t(option.label)}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</div>
<div className='grid gap-1.5'>
<Label htmlFor='default-time-granularity'>
{t('Default time granularity')}
</Label>
<Select
items={[
...TIME_GRANULARITY_OPTIONS.map((option) => ({
value: option.value,
label: t(option.label),
})),
]}
value={draft.defaultTimeGranularity}
onValueChange={(value) =>
setDraft((prev) => ({
...prev,
defaultTimeGranularity: value as TimeGranularity,
}))
}
>
<SelectTrigger id='default-time-granularity'>
<SelectValue placeholder={t('Select time granularity')} />
</SelectTrigger>
<SelectContent alignItemWithTrigger={false}>
<SelectGroup>
{TIME_GRANULARITY_OPTIONS.map((option) => (
<SelectItem key={option.value} value={option.value}>
{t(option.label)}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</div>
<div className='grid gap-1.5'>
<Label htmlFor='consumption-distribution-chart'>
{t('Default consumption chart')}
</Label>
<Select
items={[
...CONSUMPTION_DISTRIBUTION_CHART_OPTIONS.map((option) => ({
value: option.value,
label: t(option.labelKey),
})),
]}
value={draft.consumptionDistributionChart}
onValueChange={(value) =>
setDraft((prev) => ({
...prev,
consumptionDistributionChart:
value as ConsumptionDistributionChartType,
}))
}
>
<SelectTrigger id='consumption-distribution-chart'>
<SelectValue placeholder={t('Select default chart')} />
</SelectTrigger>
<SelectContent alignItemWithTrigger={false}>
<SelectGroup>
{CONSUMPTION_DISTRIBUTION_CHART_OPTIONS.map((option) => (
<SelectItem key={option.value} value={option.value}>
{t(option.labelKey)}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</div>
<div className='grid gap-1.5'>
<Label htmlFor='model-analytics-chart'>
{t('Default model call chart')}
</Label>
<Select
items={[
...MODEL_ANALYTICS_CHART_OPTIONS.map((option) => ({
value: option.value,
label: t(option.labelKey),
})),
]}
value={draft.modelAnalyticsChart}
onValueChange={(value) =>
setDraft((prev) => ({
...prev,
modelAnalyticsChart: value as ModelAnalyticsChartTab,
}))
}
>
<SelectTrigger id='model-analytics-chart'>
<SelectValue placeholder={t('Select default chart')} />
</SelectTrigger>
<SelectContent alignItemWithTrigger={false}>
<SelectGroup>
{MODEL_ANALYTICS_CHART_OPTIONS.map((option) => (
<SelectItem key={option.value} value={option.value}>
{t(option.labelKey)}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</div>
</Dialog>
)
}
@@ -23,15 +23,6 @@ import { useAuthStore } from '@/stores/auth-store'
import { getRollingDateRange, type TimeGranularity } from '@/lib/time'
import { cn } from '@/lib/utils'
import { Button } from '@/components/ui/button'
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from '@/components/ui/dialog'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { ScrollArea } from '@/components/ui/scroll-area'
@@ -44,6 +35,7 @@ import {
SelectValue,
} from '@/components/ui/select'
import { DateTimePicker } from '@/components/datetime-picker'
import { Dialog } from '@/components/dialog'
import {
TIME_GRANULARITY_OPTIONS,
TIME_RANGE_PRESETS,
@@ -144,129 +136,22 @@ export function ModelsFilter(props: ModelsFilterProps) {
}
return (
<Dialog open={open} onOpenChange={handleOpenChange}>
<DialogTrigger render={<Button variant='outline' size='sm' />}>
<Filter className='mr-2 h-4 w-4' />
{t('Filter')}
</DialogTrigger>
<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 Dashboard Models')}</DialogTitle>
<DialogDescription>
{t(
'Set filters to customize your dashboard statistics and charts.'
)}
</DialogDescription>
</DialogHeader>
<ScrollArea className='flex-1 pr-3 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='grid grid-cols-2 gap-2 sm:flex'>
{TIME_RANGE_PRESETS.map((range) => (
<Button
key={range.days}
type='button'
size='sm'
variant={
selectedRange === range.days ? 'default' : 'outline'
}
onClick={() => handleQuickRange(range.days)}
className={cn(
'flex-1',
selectedRange === range.days &&
'ring-ring ring-2 ring-offset-2'
)}
>
{t(range.label)}
</Button>
))}
</div>
</div>
<SectionDivider label={t('Custom Time Range')} />
{/* Custom time range */}
<div className='grid gap-3 sm:gap-4'>
<div className='grid gap-2'>
<Label htmlFor='start_timestamp'>{t('Start Time')}</Label>
<DateTimePicker
value={filters.start_timestamp}
onChange={(date) =>
handleChange('start_timestamp', date || undefined)
}
placeholder={t('Select start time')}
/>
</div>
<div className='grid gap-2'>
<Label htmlFor='end_timestamp'>{t('End Time')}</Label>
<DateTimePicker
value={filters.end_timestamp}
onChange={(date) =>
handleChange('end_timestamp', date || undefined)
}
placeholder={t('Select end time')}
/>
</div>
</div>
<SectionDivider label={t('Chart Settings')} />
<div className='grid gap-2'>
<Label htmlFor='time_granularity'>{t('Time Granularity')}</Label>
<Select
items={[
...TIME_GRANULARITY_OPTIONS.map((option) => ({
value: option.value,
label: t(option.label),
})),
]}
value={filters.time_granularity}
onValueChange={(value) =>
handleChange('time_granularity', value as TimeGranularity)
}
>
<SelectTrigger>
<SelectValue placeholder={t('Select time granularity')} />
</SelectTrigger>
<SelectContent alignItemWithTrigger={false}>
<SelectGroup>
{TIME_GRANULARITY_OPTIONS.map((option) => (
<SelectItem key={option.value} value={option.value}>
{t(option.label)}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</div>
{/* Admin-only fields */}
{isAdmin && (
<>
<SectionDivider label={t('Admin Only')} />
<div className='grid gap-2'>
<Label htmlFor='username'>{t('Username')}</Label>
<Input
id='username'
placeholder={t('Filter by username')}
value={filters.username}
onChange={(e) => handleChange('username', e.target.value)}
/>
</div>
</>
)}
</div>
</ScrollArea>
<DialogFooter className='grid grid-cols-2 gap-2 sm:flex'>
<Dialog
open={open}
onOpenChange={handleOpenChange}
trigger={
<Button variant='outline' size='sm'>
<Filter className='mr-2 h-4 w-4' />
{t('Filter')}
</Button>
}
title={t('Model Analytics Filters')}
description={t('Filter the model analytics view by time range and user.')}
contentClassName='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'
contentHeight='min(48vh, 460px)'
footerClassName='grid grid-cols-2 gap-2 sm:flex'
footer={
<>
<Button onClick={handleReset} variant='outline' type='button'>
<RotateCcw className='mr-2 h-4 w-4' />
{t('Reset')}
@@ -275,8 +160,113 @@ export function ModelsFilter(props: ModelsFilterProps) {
<Search className='mr-2 h-4 w-4' />
{t('Apply Filters')}
</Button>
</DialogFooter>
</DialogContent>
</>
}
>
<ScrollArea className='h-full pr-3 sm:pr-4'>
<div className='grid gap-2.5 py-2'>
{/* 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='grid grid-cols-2 gap-2 sm:flex'>
{TIME_RANGE_PRESETS.map((range) => (
<Button
key={range.days}
type='button'
size='sm'
variant={selectedRange === range.days ? 'default' : 'outline'}
onClick={() => handleQuickRange(range.days)}
className={cn(
'flex-1',
selectedRange === range.days &&
'ring-ring ring-2 ring-offset-2'
)}
>
{t(range.label)}
</Button>
))}
</div>
</div>
<SectionDivider label={t('Custom Time Range')} />
{/* Custom time range */}
<div className='grid gap-2.5'>
<div className='grid gap-2'>
<Label htmlFor='start_timestamp'>{t('Start Time')}</Label>
<DateTimePicker
value={filters.start_timestamp}
onChange={(date) =>
handleChange('start_timestamp', date || undefined)
}
placeholder={t('Select start time')}
/>
</div>
<div className='grid gap-2'>
<Label htmlFor='end_timestamp'>{t('End Time')}</Label>
<DateTimePicker
value={filters.end_timestamp}
onChange={(date) =>
handleChange('end_timestamp', date || undefined)
}
placeholder={t('Select end time')}
/>
</div>
</div>
<SectionDivider label={t('Chart Settings')} />
<div className='grid gap-2'>
<Label htmlFor='time_granularity'>{t('Time Granularity')}</Label>
<Select
items={[
...TIME_GRANULARITY_OPTIONS.map((option) => ({
value: option.value,
label: t(option.label),
})),
]}
value={filters.time_granularity}
onValueChange={(value) =>
handleChange('time_granularity', value as TimeGranularity)
}
>
<SelectTrigger>
<SelectValue placeholder={t('Select time granularity')} />
</SelectTrigger>
<SelectContent alignItemWithTrigger={false}>
<SelectGroup>
{TIME_GRANULARITY_OPTIONS.map((option) => (
<SelectItem key={option.value} value={option.value}>
{t(option.label)}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</div>
{/* Admin-only fields */}
{isAdmin && (
<>
<SectionDivider label={t('Admin Only')} />
<div className='grid gap-2'>
<Label htmlFor='username'>{t('Username')}</Label>
<Input
id='username'
placeholder={t('Filter by username')}
value={filters.username}
onChange={(e) => handleChange('username', e.target.value)}
/>
</div>
</>
)}
</div>
</ScrollArea>
</Dialog>
)
}
@@ -18,15 +18,9 @@ For commercial licensing, please contact support@quantumnous.com
*/
import { useTranslation } from 'react-i18next'
import { formatDateTimeObject } from '@/lib/time'
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog'
import { Markdown } from '@/components/ui/markdown'
import { ScrollArea } from '@/components/ui/scroll-area'
import { Dialog } from '@/components/dialog'
interface AnnouncementDetailModalProps {
open: boolean
@@ -47,38 +41,39 @@ export function AnnouncementDetailModal({
}: AnnouncementDetailModalProps) {
const { t } = useTranslation()
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className='sm:max-w-lg'>
<DialogHeader>
<DialogTitle>{t('Announcement Details')}</DialogTitle>
{announcement?.publishDate && (
<DialogDescription>
{t('Published:')}{' '}
{formatDateTimeObject(new Date(announcement.publishDate))}
</DialogDescription>
<Dialog
open={open}
onOpenChange={onOpenChange}
title={t('Announcement Details')}
description={
announcement?.publishDate
? `${t('Published:')} ${formatDateTimeObject(new Date(announcement.publishDate))}`
: undefined
}
contentClassName='sm:max-w-lg'
contentHeight='auto'
bodyClassName='space-y-4'
>
<ScrollArea className='max-h-[min(58vh,520px)] pr-4'>
<div className='space-y-4'>
{announcement?.content && (
<div>
<h4 className='mb-2 font-medium'>{t('Content')}</h4>
<Markdown>{announcement.content}</Markdown>
</div>
)}
</DialogHeader>
<ScrollArea className='max-h-[60vh] pr-4'>
<div className='space-y-4'>
{announcement?.content && (
<div>
<h4 className='mb-2 font-medium'>{t('Content')}</h4>
<Markdown>{announcement.content}</Markdown>
</div>
)}
{announcement?.extra && (
<div>
<h4 className='mb-2 font-medium'>
{t('Additional Information')}
</h4>
<Markdown className='text-muted-foreground'>
{announcement.extra}
</Markdown>
</div>
)}
</div>
</ScrollArea>
</DialogContent>
{announcement?.extra && (
<div>
<h4 className='mb-2 font-medium'>
{t('Additional Information')}
</h4>
<Markdown className='text-muted-foreground'>
{announcement.extra}
</Markdown>
</div>
)}
</div>
</ScrollArea>
</Dialog>
)
}