Add time range controls bar above sparkline with granularity selector and refresh button
This commit is contained in:
parent
d9dc7e0845
commit
959cb6e4cf
|
|
@ -17,8 +17,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
import { TrendingUp } from 'lucide-react';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { TrendingUp, RefreshCw, ChevronDown } from 'lucide-react';
|
||||
import Sparkline from './Sparkline';
|
||||
import { renderQuota } from '../../helpers';
|
||||
|
||||
|
|
@ -28,11 +28,21 @@ const formatMD = (d) => {
|
|||
return `${m}/${day}`;
|
||||
};
|
||||
|
||||
const GRANULARITY = [
|
||||
{ value: 'hour', label: '按小时' },
|
||||
{ value: 'day', label: '按天' },
|
||||
{ value: 'week', label: '按周' },
|
||||
{ value: 'month', label: '按月' },
|
||||
];
|
||||
|
||||
const BentoTrend = ({
|
||||
dailyStats = [],
|
||||
topModels = [],
|
||||
className = '',
|
||||
onRefresh,
|
||||
loading = false,
|
||||
}) => {
|
||||
const [granularity, setGranularity] = useState('day');
|
||||
const counts = useMemo(() => dailyStats.map((d) => d.count), [dailyStats]);
|
||||
const costs = useMemo(() => dailyStats.map((d) => d.cost), [dailyStats]);
|
||||
|
||||
|
|
@ -71,6 +81,39 @@ const BentoTrend = ({
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex items-center justify-between'>
|
||||
<div className='flex items-center gap-2'>
|
||||
<span className='inline-flex items-center gap-1 rounded-full bg-slate-100 px-3 py-1 text-xs font-medium text-slate-700 dark:bg-white/5 dark:text-white/70'>
|
||||
近 7 天
|
||||
</span>
|
||||
<button
|
||||
onClick={onRefresh}
|
||||
disabled={loading}
|
||||
className='flex h-7 w-7 items-center justify-center rounded-full border border-slate-200 bg-white text-slate-500 transition-colors hover:border-slate-300 hover:bg-slate-50 hover:text-slate-700 disabled:opacity-50 dark:border-white/10 dark:bg-white/[0.02] dark:text-white/60 dark:hover:bg-white/5 dark:hover:text-white'
|
||||
title='刷新'
|
||||
>
|
||||
<RefreshCw size={12} className={loading ? 'animate-spin' : ''} />
|
||||
</button>
|
||||
</div>
|
||||
<div className='flex items-center gap-1.5'>
|
||||
<span className='text-[10px] text-slate-400 dark:text-white/40'>粒度</span>
|
||||
<div className='relative'>
|
||||
<select
|
||||
value={granularity}
|
||||
onChange={(e) => setGranularity(e.target.value)}
|
||||
className='appearance-none rounded-lg border border-slate-200 bg-white py-1 pl-2.5 pr-7 text-xs text-slate-700 outline-none transition-colors hover:border-slate-300 focus:border-slate-400 dark:border-white/10 dark:bg-white/[0.02] dark:text-white/70 dark:hover:bg-white/5 dark:focus:border-white/30'
|
||||
>
|
||||
{GRANULARITY.map((g) => (
|
||||
<option key={g.value} value={g.value} className='bg-white text-slate-900 dark:bg-neutral-900 dark:text-white'>
|
||||
{g.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<ChevronDown size={12} className='pointer-events-none absolute right-1.5 top-1/2 -translate-y-1/2 text-slate-400 dark:text-white/40' />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex min-h-[80px] flex-1 items-end text-blue-500'>
|
||||
<Sparkline data={counts} width={400} height={80} />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -64,6 +64,8 @@ const Dashboard = () => {
|
|||
<BentoTrend
|
||||
dailyStats={dashboard.dailyStats}
|
||||
topModels={dashboard.topModels}
|
||||
onRefresh={dashboard.refresh}
|
||||
loading={dashboard.loading}
|
||||
className='md:col-span-2'
|
||||
/>
|
||||
<MonthlyForecast
|
||||
|
|
|
|||
Loading…
Reference in New Issue