From daa3d7450012981d553ff5213669ec04eeaea0ca Mon Sep 17 00:00:00 2001 From: jialin Date: Mon, 20 Apr 2026 15:07:25 +0800 Subject: [PATCH] fix: usage datepicker --- src/components/seal-form/simple-select.tsx | 6 +- .../dashboard/hooks/use-rangepicker-preset.ts | 80 +++++++++++++++++-- src/pages/usage/components/breakdown-tabs.tsx | 3 + src/pages/usage/components/daily-usage.tsx | 11 +-- src/pages/usage/components/filter-bar.tsx | 80 +++++++++++-------- src/pages/usage/config/types.ts | 2 +- src/pages/usage/hooks/use-usage-filters.ts | 1 + .../usage/services/use-query-meta-data.ts | 23 +++++- 8 files changed, 157 insertions(+), 49 deletions(-) diff --git a/src/components/seal-form/simple-select.tsx b/src/components/seal-form/simple-select.tsx index 89c38978..9e4754a9 100644 --- a/src/components/seal-form/simple-select.tsx +++ b/src/components/seal-form/simple-select.tsx @@ -47,6 +47,7 @@ const SimpleSelect: React.FC< SelectProps & { ref?: any; showTags?: boolean; + optionLabelRender?: (option: any) => React.ReactNode; styles?: { wrapper?: React.CSSProperties; select?: React.CSSProperties; @@ -58,6 +59,7 @@ const SimpleSelect: React.FC< options = [], showTags: tagsVisible, styles = {}, + optionLabelRender, maxTagCount: tagCount, ...restProps } = props; @@ -90,7 +92,9 @@ const SimpleSelect: React.FC< ) : ( )} - {label} + + {optionLabelRender ? optionLabelRender(option) : label} + ); }; diff --git a/src/pages/dashboard/hooks/use-rangepicker-preset.ts b/src/pages/dashboard/hooks/use-rangepicker-preset.ts index 024756b9..47097c88 100644 --- a/src/pages/dashboard/hooks/use-rangepicker-preset.ts +++ b/src/pages/dashboard/hooks/use-rangepicker-preset.ts @@ -18,6 +18,11 @@ export default function useRangePickerPreset(options?: RangePickerPreset): { label: React.ReactNode; value: [Dayjs, Dayjs] | (() => [Dayjs, Dayjs]); }[]; + normalizeRangeValue: ( + startDate: string, + endDate: string, + picker: 'date' | 'week' | 'month' | 'quarter' | 'year' + ) => [Dayjs, Dayjs]; handleOnPickerChange: ( value: 'date' | 'week' | 'month' | 'quarter' | 'year' ) => void; @@ -28,22 +33,76 @@ export default function useRangePickerPreset(options?: RangePickerPreset): { const intl = useIntl(); const [picker, setPicker] = useState< 'date' | 'week' | 'month' | 'quarter' | 'year' - >('month'); + >('date'); const getYearMonth = (date: Dayjs) => date.year() * 12 + date.month(); + const normalizeRangeValue = ( + startDate: string, + endDate: string, + picker: 'date' | 'week' | 'month' | 'quarter' | 'year' + ): [Dayjs, Dayjs] => { + const start = dayjs(startDate); + const end = dayjs(endDate); + + switch (picker) { + case 'week': + return [start.startOf('week'), end.endOf('week')]; + + case 'month': + return [start.startOf('month'), end.endOf('month')]; + + case 'year': + return [start.startOf('year'), end.endOf('year')]; + + default: + return [start, end]; + } + }; + + const getRangeBoundary = ( + from: Dayjs, + range: number, + type: 'date' | 'week' | 'month' | 'quarter' | 'year' + ) => { + switch (type) { + case 'year': + return { + minDate: from.add(1 - range, 'year'), + maxDate: from.add(range - 1, 'year') + }; + + case 'month': + return { + minDate: from.add(1 - range, 'month'), + maxDate: from.add(range - 1, 'month') + }; + + case 'week': + return { + minDate: from.add(1 - range, 'week'), + maxDate: from.add(range - 1, 'week') + }; + + default: + return { + minDate: from.add(1 - range, 'day'), + maxDate: from.add(range - 1, 'day') + }; + } + }; + const disabledRangeDaysDate: DatePickerProps['disabledDate'] = ( current, { from, type } ) => { - if (!disabledDate) { + if (!disabledDate || !from) { return false; } - if (from) { - const minDate = from.add(1 - range, 'days'); - const maxDate = from.add(range - 1, 'days'); + const { minDate, maxDate } = getRangeBoundary(from, range, type as any); + if (from) { switch (type) { case 'year': return ( @@ -56,6 +115,16 @@ export default function useRangePickerPreset(options?: RangePickerPreset): { getYearMonth(current) > getYearMonth(maxDate) ); + case 'week': { + const currentWeekStart = current.startOf('week'); + const currentWeekEnd = current.endOf('week'); + + return ( + currentWeekEnd.isBefore(minDate, 'day') || + currentWeekStart.isAfter(maxDate, 'day') + ); + } + default: return current.isBefore(minDate) || current.isAfter(maxDate); } @@ -94,6 +163,7 @@ export default function useRangePickerPreset(options?: RangePickerPreset): { return { disabledRangeDaysDate, handleOnPickerChange, + normalizeRangeValue, rangePresets, picker, range diff --git a/src/pages/usage/components/breakdown-tabs.tsx b/src/pages/usage/components/breakdown-tabs.tsx index d55e1727..60e1d6ae 100644 --- a/src/pages/usage/components/breakdown-tabs.tsx +++ b/src/pages/usage/components/breakdown-tabs.tsx @@ -28,6 +28,7 @@ const BreakdownTabs: React.FC<{ forceRender: true, children: ( = (props) => { prefix={Metric} options={metricOptions} value={metric} + popupMatchSelectWidth={false} onChange={onMetricChange} - style={{ width: 200 }} + style={{ width: 180 }} /> = (props) => { prefix={Group by} options={groupByOptions} value={groupBy} + popupMatchSelectWidth={false} onChange={onGroupByChange} - style={{ width: 200 }} /> - {/* */} {/* */} = (props) => { handleSearch } = props; const intl = useIntl(); - const { disabledRangeDaysDate, rangePresets, picker, handleOnPickerChange } = - useRangePickerPreset({ - range: DefaultDateConfig.maxRange, - disabledDate: true, - presetRanges: [ - { - label: intl.formatMessage({ - id: 'dashboard.usage.datePicker.last7days' - }), - value: [dayjs().add(-6, 'd'), dayjs()] - }, - { - label: intl.formatMessage({ - id: 'dashboard.usage.datePicker.last30days' - }), - value: [dayjs().add(-29, 'd'), dayjs()] - }, - { - label: intl.formatMessage({ - id: 'dashboard.usage.datePicker.last60days' - }), - value: [dayjs().add(-59, 'd'), dayjs()] - }, - { - label: intl.formatMessage({ - id: 'dashboard.usage.datePicker.last90days' - }), - value: [dayjs().add(-89, 'd'), dayjs()] - } - ] - }); + const { + disabledRangeDaysDate, + rangePresets, + picker, + normalizeRangeValue, + handleOnPickerChange + } = useRangePickerPreset({ + range: DefaultDateConfig.maxRange, + disabledDate: true, + presetRanges: [ + { + label: intl.formatMessage({ + id: 'dashboard.usage.datePicker.last7days' + }), + value: [dayjs().add(-6, 'd'), dayjs()] + }, + { + label: intl.formatMessage({ + id: 'dashboard.usage.datePicker.last30days' + }), + value: [dayjs().add(-29, 'd'), dayjs()] + }, + { + label: intl.formatMessage({ + id: 'dashboard.usage.datePicker.last60days' + }), + value: [dayjs().add(-59, 'd'), dayjs()] + }, + { + label: intl.formatMessage({ + id: 'dashboard.usage.datePicker.last90days' + }), + value: [dayjs().add(-89, 'd'), dayjs()] + } + ] + }); const [activeModels, setActiveModels] = React.useState([]); const [activeApiKeys, setActiveApiKeys] = React.useState([]); @@ -222,6 +227,16 @@ const FilterBar: React.FC = (props) => { ); }; + const userOptionRender = (option: any) => { + const { data } = option; + return ( + + {data.label} + {data.isCurrent && [Current]} + + ); + }; + return (
@@ -231,12 +246,12 @@ const FilterBar: React.FC = (props) => { dayjs().add(-DefaultDateConfig.defaultRange, 'd'), dayjs() ]} + format={'YYYY-MM-DD'} picker={picker} disabledDate={disabledRangeDaysDate} presets={rangePresets} allowClear={false} style={{ width: 240 }} - value={[dayjs(startDate), dayjs(endDate)]} onChange={onDateChange} renderExtraFooter={renderFooter} /> @@ -294,6 +309,7 @@ const FilterBar: React.FC = (props) => { wrapper: { flex: 1, maxWidth: 240, minWidth: 150 } }} value={selectedUsers} + optionLabelRender={userOptionRender} onChange={onUsersChange} />
{ + console.log('Selected Time: ', dateStrings); const [start_date, end_date] = dateStrings; const next = { ...commonFilters, start_date, end_date }; setCommonFilters(next); diff --git a/src/pages/usage/services/use-query-meta-data.ts b/src/pages/usage/services/use-query-meta-data.ts index cb06e42c..33be9293 100644 --- a/src/pages/usage/services/use-query-meta-data.ts +++ b/src/pages/usage/services/use-query-meta-data.ts @@ -1,4 +1,5 @@ import { useQueryData } from '@/hooks/use-query-data-list'; +import { useModel } from '@@/plugin-model'; import { useState } from 'react'; import { queryUsageMetaData } from '../apis'; import { GroupOption, groupToOptions } from '../config'; @@ -14,7 +15,7 @@ export default function useQueryUsageMetaData() { fetchDetail: queryUsageMetaData, key: 'usageMetaData' }); - + const { initialState } = useModel('@@initialState'); const [result, setResult] = useState<{ models: GroupOption[]; users: UserOptionType[]; @@ -25,9 +26,23 @@ export default function useQueryUsageMetaData() { api_keys: [] }); + // the current user sort in the first place + const sortUsers = (users: UsageFilterItem[]) => { + const currentUserId = initialState?.currentUser?.id; + if (!currentUserId) return users; + + const sortedUsers = [...users].sort((a, b) => { + if (a.identity.current.user_id === currentUserId) return -1; + if (b.identity.current.user_id === currentUserId) return 1; + return 0; + }); + + return sortedUsers; + }; + const queryMetaData = async () => { const res = await fetchData({}); - + const sortedUsers = sortUsers(res?.filters?.users || []); const data = { models: groupToOptions(res?.filters?.models || [], { getGroupKey: (item) => item.identity.value.provider_name || 'gpustack', @@ -40,8 +55,10 @@ export default function useQueryUsageMetaData() { }) }), users: - res?.filters?.users?.map((item) => ({ + sortedUsers.map((item) => ({ value: item.label, + isCurrent: + item.identity.current.user_id === initialState?.currentUser?.id, ...item })) || [], api_keys: groupToOptions(res?.filters?.api_keys || [], {