fix: usage datepicker

This commit is contained in:
jialin
2026-04-22 13:16:48 +08:00
committed by jialin
parent ad7fc3a993
commit daa3d74500
8 changed files with 157 additions and 49 deletions
+5 -1
View File
@@ -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<
) : (
<Checkbox></Checkbox>
)}
<AutoTooltip ghost>{label}</AutoTooltip>
<AutoTooltip ghost>
{optionLabelRender ? optionLabelRender(option) : label}
</AutoTooltip>
</OptionWrapper>
);
};
@@ -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
@@ -28,6 +28,7 @@ const BreakdownTabs: React.FC<{
forceRender: true,
children: (
<ModelsTable
key="models"
models={filters.models || []}
dateRange={dateRange}
scope={scope}
@@ -40,6 +41,7 @@ const BreakdownTabs: React.FC<{
forceRender: true,
children: (
<UsersTable
key="users"
users={filters.users || []}
dateRange={dateRange}
scope={scope}
@@ -52,6 +54,7 @@ const BreakdownTabs: React.FC<{
forceRender: true,
children: (
<ApiKeysTable
key="api_keys"
apiKeys={filters.api_keys || []}
dateRange={dateRange}
scope={scope}
+4 -7
View File
@@ -25,8 +25,6 @@ const ControlsWrapper = styled.div`
const ControlLabel = styled.span`
font-size: 14px;
color: var(--ant-color-text-tertiary);
border-right: 1px solid var(--ant-color-split);
padding-right: 8px;
margin-right: 8px;
`;
@@ -154,8 +152,9 @@ const DailyUsage: React.FC<DailyUsageProps> = (props) => {
prefix={<ControlLabel>Metric</ControlLabel>}
options={metricOptions}
value={metric}
popupMatchSelectWidth={false}
onChange={onMetricChange}
style={{ width: 200 }}
style={{ width: 180 }}
/>
<BaseSelect
@@ -163,19 +162,17 @@ const DailyUsage: React.FC<DailyUsageProps> = (props) => {
prefix={<ControlLabel>Group by</ControlLabel>}
options={groupByOptions}
value={groupBy}
popupMatchSelectWidth={false}
onChange={onGroupByChange}
style={{ width: 200 }}
/>
</div>
<Segmented
size="middle"
shape="round"
size="small"
options={granularities}
value={granularity}
onChange={onGranularityChange}
></Segmented>
</ControlsWrapper>
{/* <Divider style={{ margin: 0 }} /> */}
{/* <SimpleCard dataList={summaryCards} height={80} /> */}
<MixLineBar
+48 -32
View File
@@ -62,37 +62,42 @@ const FilterBar: React.FC<FilterBarProps> = (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<valueType[][]>([]);
const [activeApiKeys, setActiveApiKeys] = React.useState<valueType[][]>([]);
@@ -222,6 +227,16 @@ const FilterBar: React.FC<FilterBarProps> = (props) => {
);
};
const userOptionRender = (option: any) => {
const { data } = option;
return (
<span className="flex-center gap-8">
<span>{data.label}</span>
{data.isCurrent && <span className="text-tertiary"> [Current]</span>}
</span>
);
};
return (
<div className={FilterBarCss.wrapper}>
<div className={FilterBarCss.filters}>
@@ -231,12 +246,12 @@ const FilterBar: React.FC<FilterBarProps> = (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<FilterBarProps> = (props) => {
wrapper: { flex: 1, maxWidth: 240, minWidth: 150 }
}}
value={selectedUsers}
optionLabelRender={userOptionRender}
onChange={onUsersChange}
/>
<div
+1 -1
View File
@@ -12,7 +12,7 @@ export interface UsageFilterItem {
};
current: {
model_id: string | null;
user_id: string | null;
user_id: number | null;
api_key_id: string | null;
};
};
@@ -129,6 +129,7 @@ export const useUsageFilters = ({
};
const handleDateChange = (_: any, dateStrings: [string, string]) => {
console.log('Selected Time: ', dateStrings);
const [start_date, end_date] = dateStrings;
const next = { ...commonFilters, start_date, end_date };
setCommonFilters(next);
@@ -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<UsageFilterItem>[];
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 || [], {