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:
+153
-168
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
+124
-134
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
+33
-38
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user