💄 style: fix usage table display issues (#10108)

* wip: use stack bar chart

* 💄 style: update labels

* 🐛 fix: should not include INF vales

* ♻️ refactor: improve codes

* 💄 style: improve label format

* 💄 style: improve label format
This commit is contained in:
Rylan Cai
2026-01-30 01:05:02 +08:00
committed by GitHub
parent 891837b792
commit 4bd82c397a
4 changed files with 69 additions and 29 deletions

View File

@@ -2,43 +2,66 @@ import { BarChart, type BarChartProps, ChartTooltipFrame, ChartTooltipRow } from
import { Flexbox, Text } from '@lobehub/ui';
import { Divider } from 'antd';
export const UsageBarChart = ({ ...props }: BarChartProps) => (
import { formatNumber, formatTokenNumber } from '@/utils/format';
interface UsageBarChartProps extends BarChartProps {
showType: 'spend' | 'token';
}
export const UsageBarChart = ({ ...props }: UsageBarChartProps) => (
<BarChart
{...props}
customTooltip={({ active, payload, label, valueFormatter }) => {
customTooltip={({ active, payload, label }) => {
if (active && payload) {
const sum = payload.reduce(
(acc: number, cur: any) => (typeof cur.value === 'number' ? acc + cur.value : acc),
0,
);
return (
<ChartTooltipFrame>
<Flexbox horizontal justify={'space-between'} paddingBlock={8} paddingInline={16}>
<Text as={'p'} ellipsis style={{ margin: 0 }}>
{label}
</Text>
<span style={{ fontWeight: 'bold' }}>
{payload.reduce((acc: number, cur: any) => acc + cur.value, 0)}
</span>
</Flexbox>
<Divider style={{ margin: 0 }} />
<Flexbox
gap={4}
paddingBlock={8}
paddingInline={16}
style={{ flexDirection: 'column-reverse', marginTop: 4 }}
>
{payload.map(({ value, color, name }: any, idx: number) =>
typeof value === 'number' && value > 0 ? (
<ChartTooltipRow
color={color}
key={`id-${idx}`}
name={name}
value={(valueFormatter as any)?.(value)}
/>
) : null,
{sum !== 0 && (
<span style={{ fontWeight: 'bold' }}>
{props.showType === 'spend' ? formatNumber(sum, 2) : formatTokenNumber(sum)}
</span>
)}
</Flexbox>
{sum !== 0 && (
<>
<Divider style={{ margin: 0 }} />
<Flexbox
gap={4}
paddingBlock={8}
paddingInline={16}
style={{ flexDirection: 'column-reverse', marginTop: 4 }}
>
{payload.map(({ value, color, name }: any, idx: number) =>
typeof value === 'number' && value > 0 ? (
<ChartTooltipRow
color={color}
key={`id-${idx}`}
name={name}
value={
props.showType === 'spend'
? formatNumber(value, 2)
: formatTokenNumber(value)
}
/>
) : null,
)}
</Flexbox>
</>
)}
</ChartTooltipFrame>
);
}
return null;
}}
valueFormatter={(num) =>
props.showType === 'spend' ? formatNumber(num, 2) : formatTokenNumber(num)
}
/>
);

View File

@@ -1,5 +1,5 @@
import { ProviderIcon } from '@lobehub/icons';
import { Flexbox, Tag, Text } from '@lobehub/ui';
import { Flexbox, Tag, Text, Tooltip } from '@lobehub/ui';
import { type TableColumnType } from 'antd';
import { cssVar } from 'antd-style';
import { memo, useEffect } from 'react';
@@ -53,7 +53,9 @@ const UsageTable = memo<UsageChartProps>(({ dateStrings }) => {
marginRight: -8,
}}
/>
<Text>{value?.length > 12 ? `${value.slice(0, 12)}...` : value}</Text>
<Tooltip title={value}>
<Text>{value?.length > 12 ? `${value.slice(0, 12)}...` : value}</Text>
</Tooltip>
</Flexbox>
),
title: t('usage.table.model'),

View File

@@ -83,9 +83,21 @@ const UsageTrends = memo<UsageChartProps>(({ isLoading, data, groupBy }) => {
const charts =
data &&
(type === ShowType.Spend ? (
<UsageBarChart categories={spendCate} data={spendData} index="day" />
<UsageBarChart
categories={spendCate}
data={spendData}
index="day"
showType="spend"
stack={true}
/>
) : (
<UsageBarChart categories={tokenCate} data={tokenData} index="day" />
<UsageBarChart
categories={tokenCate}
data={tokenData}
index="day"
showType="token"
stack={true}
/>
));
return (

View File

@@ -28,7 +28,7 @@ export class UsageRecordService {
// Set startAt and endAt
let startAt: string;
let endAt: string;
if (mo) {
if (mo && dayjs(mo, 'YYYY-MM', true).isValid()) {
// mo format: "YYYY-MM"
startAt = dayjs(mo, 'YYYY-MM').startOf('month').format('YYYY-MM-DD');
endAt = dayjs(mo, 'YYYY-MM').endOf('month').format('YYYY-MM-DD');
@@ -89,15 +89,18 @@ export class UsageRecordService {
// Set startAt and endAt
let startAt: string;
let endAt: string;
if (mo) {
let month: string;
if (mo && dayjs(mo, 'YYYY-MM', true).isValid()) {
// mo format: "YYYY-MM"
startAt = dayjs(mo, 'YYYY-MM').startOf('month').format('YYYY-MM-DD');
endAt = dayjs(mo, 'YYYY-MM').endOf('month').format('YYYY-MM-DD');
month = mo;
} else {
startAt = dayjs().startOf('month').format('YYYY-MM-DD');
endAt = dayjs().endOf('month').format('YYYY-MM-DD');
month = dayjs().format('YYYY-MM');
}
const spends = await this.findByMonth(mo);
const spends = await this.findByMonth(month);
// Clustering by time
let usages = new Map<string, { date: Date; logs: UsageRecordItem[] }>();
spends.forEach((spend) => {