Skip to content

Commit e0273dc

Browse files
authored
🐛 Fix data collection filters (#373)
1 parent abff517 commit e0273dc

File tree

9 files changed

+216
-49
lines changed

9 files changed

+216
-49
lines changed

frontend/src/components/SearchControls.tsx

Lines changed: 145 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
ReloadOutlined,
77
} from "@ant-design/icons";
88
import { useEffect, useState } from "react";
9+
import React from "react";
910
import { useTranslation } from "react-i18next";
1011

1112
interface FilterOption {
@@ -56,7 +57,8 @@ export function SearchControls({
5657
showViewToggle = true,
5758
searchPlaceholder,
5859
filters = [],
59-
dateRange,
60+
selectedFilters: externalSelectedFilters,
61+
dateRange: externalDateRange,
6062
showDatePicker = false,
6163
showReload = true,
6264
onReload,
@@ -67,10 +69,36 @@ export function SearchControls({
6769
onClearFilters,
6870
}: SearchControlsProps) {
6971
const { t } = useTranslation();
70-
const [selectedFilters, setSelectedFilters] = useState<{
72+
73+
// 内部状态(如果外部没有传入 selectedFilters)
74+
const [internalSelectedFilters, setInternalSelectedFilters] = useState<{
7175
[key: string]: string[];
7276
}>({});
7377

78+
// 使用外部传入的 selectedFilters,如果没有则使用内部状态
79+
const selectedFilters = externalSelectedFilters !== undefined
80+
? externalSelectedFilters
81+
: internalSelectedFilters;
82+
83+
// 内部 dateRange 状态(用于禁用逻辑)
84+
const [internalDateRange, setInternalDateRange] = useState<typeof externalDateRange>(externalDateRange);
85+
86+
// 同步外部 dateRange 到内部状态
87+
useEffect(() => {
88+
setInternalDateRange(externalDateRange);
89+
}, [externalDateRange]);
90+
91+
// 更新筛选值的函数
92+
const updateSelectedFilters = (newFilters: Record<string, string[]>) => {
93+
if (externalSelectedFilters !== undefined) {
94+
// 受控模式:直接调用 onFiltersChange
95+
onFiltersChange?.(newFilters);
96+
} else {
97+
// 非受控模式:更新内部状态
98+
setInternalSelectedFilters(newFilters);
99+
}
100+
};
101+
74102
const filtersMap: Record<string, FilterOption> = filters.reduce(
75103
(prev, cur) => ({ ...prev, [cur.key]: cur }),
76104
{}
@@ -80,42 +108,51 @@ export function SearchControls({
80108
const handleFilterChange = (filterKey: string, value: string) => {
81109
const filteredValues = {
82110
...selectedFilters,
83-
[filterKey]: !value ? [] : [value],
111+
[filterKey]: !value || value === 'all' ? [] : [value],
84112
};
85-
setSelectedFilters(filteredValues);
113+
updateSelectedFilters(filteredValues);
86114
};
87115

88116
// 清除已选筛选
89117
const handleClearFilter = (filterKey: string, value: string | string[]) => {
90118
const isMultiple = filtersMap[filterKey]?.mode === "multiple";
91119
if (!isMultiple) {
92-
setSelectedFilters({
120+
updateSelectedFilters({
93121
...selectedFilters,
94122
[filterKey]: [],
95123
});
96124
} else {
97125
const currentValues = selectedFilters[filterKey]?.[0] || [];
98126
const newValues = currentValues.filter((v) => v !== value);
99-
setSelectedFilters({
127+
updateSelectedFilters({
100128
...selectedFilters,
101129
[filterKey]: [newValues],
102130
});
103131
}
104132
};
105133

106134
const handleClearAllFilters = () => {
107-
setSelectedFilters({});
135+
updateSelectedFilters({});
108136
onClearFilters?.();
109137
};
110138

111139
const hasActiveFilters = Object.values(selectedFilters).some(
112-
(values) => values?.[0]?.length > 0
140+
(values) => Array.isArray(values) && values.length > 0 && values[0] !== undefined
113141
);
114142

143+
// 同步外部 selectedFilters 到内部状态
115144
useEffect(() => {
145+
if (externalSelectedFilters !== undefined) {
146+
setInternalSelectedFilters(externalSelectedFilters);
147+
}
148+
}, [externalSelectedFilters]);
149+
150+
// 非受控模式下,当内部状态变化时通知父组件
151+
useEffect(() => {
152+
if (externalSelectedFilters !== undefined) return; // 受控模式不需要这个 effect
116153
if (Object.keys(selectedFilters).length === 0) return;
117154
onFiltersChange?.(selectedFilters);
118-
}, [selectedFilters]);
155+
}, [selectedFilters, onFiltersChange, externalSelectedFilters]);
119156

120157
return (
121158
<div className={className}>
@@ -160,11 +197,79 @@ export function SearchControls({
160197

161198
{showDatePicker && (
162199
<DatePicker.RangePicker
163-
value={dateRange as any}
164-
onChange={onDateChange}
165-
style={{ width: 260 }}
200+
value={internalDateRange as any}
201+
onChange={(date) => {
202+
setInternalDateRange(date);
203+
onDateChange?.(date);
204+
}}
205+
showTime={{ format: 'HH:mm:ss' }}
206+
format="YYYY-MM-DD HH:mm:ss"
207+
style={{ width: 380 }}
166208
allowClear
167209
placeholder={[t('components.searchControls.startTime'), t('components.searchControls.endTime')]}
210+
disabledDate={(current, info) => {
211+
// 只禁用日期部分,同一天不禁用
212+
const startDate = info.from;
213+
if (!startDate) {
214+
return false;
215+
}
216+
// 如果是同一天,不禁用(让时间选择器处理)
217+
if (current.isSame(startDate, 'day')) {
218+
return false;
219+
}
220+
// 禁用早于开始日期的日期
221+
return current.isBefore(startDate, 'day');
222+
}}
223+
disabledTime={(current, partial, info) => {
224+
// partial 是 'start' 或 'end',表示当前正在选择哪个
225+
// info.from 是已选择的开始日期
226+
const startDate = info.from;
227+
228+
if (partial !== 'end' || !startDate) {
229+
return {
230+
disabledHours: () => [],
231+
disabledMinutes: () => [],
232+
disabledSeconds: () => [],
233+
};
234+
}
235+
236+
const startHour = startDate.hour();
237+
const startMinute = startDate.minute();
238+
const startSecond = startDate.second();
239+
240+
return {
241+
disabledHours: () => {
242+
const hours = [];
243+
for (let i = 0; i < startHour; i++) {
244+
hours.push(i);
245+
}
246+
return hours;
247+
},
248+
disabledMinutes: (selectedHour) => {
249+
if (selectedHour > startHour) {
250+
return [];
251+
}
252+
const minutes = [];
253+
for (let i = 0; i < startMinute; i++) {
254+
minutes.push(i);
255+
}
256+
return minutes;
257+
},
258+
disabledSeconds: (selectedHour, selectedMinute) => {
259+
if (selectedHour > startHour) {
260+
return [];
261+
}
262+
if (selectedHour === startHour && selectedMinute > startMinute) {
263+
return [];
264+
}
265+
const seconds = [];
266+
for (let i = 0; i < startSecond; i++) {
267+
seconds.push(i);
268+
}
269+
return seconds;
270+
},
271+
};
272+
}}
168273
/>
169274
)}
170275

@@ -191,15 +296,16 @@ export function SearchControls({
191296
</div>
192297

193298
{/* Active Filters Display */}
194-
{hasActiveFilters && (
299+
{(hasActiveFilters || internalDateRange) && (
195300
<div className="mt-4 pt-4 border-t border-gray-200">
196301
<div className="flex items-center justify-between">
197302
<div className="flex items-center gap-2 flex-wrap flex-1">
198303
<span className="text-sm font-medium text-gray-700">
199304
{t('components.searchControls.selectedFilters')}
200305
</span>
201306
{Object.entries(selectedFilters).map(([filterKey, values]) =>
202-
values.map((value) => {
307+
// 只处理数组类型的筛选值
308+
Array.isArray(values) && values.map((value) => {
203309
const filter = filtersMap[filterKey];
204310

205311
const getLabeledValue = (item: string) => {
@@ -222,16 +328,39 @@ export function SearchControls({
222328
: getLabeledValue(value);
223329
})
224330
)}
331+
{/* 显示时间范围标签 */}
332+
{internalDateRange && internalDateRange[0] && (
333+
<Tag closable onClose={() => {
334+
setInternalDateRange(null);
335+
onDateChange?.(null);
336+
}} color="blue">
337+
{t('components.searchControls.startTime')}: {internalDateRange[0]?.format('YYYY-MM-DD HH:mm:ss')}
338+
</Tag>
339+
)}
340+
{internalDateRange && internalDateRange[1] && (
341+
<React.Fragment key="time-separator">
342+
<span className="text-gray-400 mx-1">~</span>
343+
<Tag closable onClose={() => {
344+
setInternalDateRange(null);
345+
onDateChange?.(null);
346+
}} color="blue">
347+
{t('components.searchControls.endTime')}: {internalDateRange[1]?.format('YYYY-MM-DD HH:mm:ss')}
348+
</Tag>
349+
</React.Fragment>
350+
)}
225351
</div>
226352

227353
{/* Clear all filters button on the right */}
228354
<Button
229355
type="text"
230356
size="small"
231-
onClick={handleClearAllFilters}
357+
onClick={() => {
358+
handleClearAllFilters();
359+
onDateChange?.(null);
360+
}}
232361
className="text-gray-500 hover:text-gray-700"
233362
>
234-
{t('components.searchControls.clearAll')}
363+
{t('components.searchControls.filters.clearAll')}
235364
</Button>
236365
</div>
237366
</div>

frontend/src/hooks/useFetchData.ts

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -114,18 +114,28 @@ export default function useFetchData<T>(
114114

115115
try {
116116
// 同时执行主要数据获取和额外的轮询函数
117+
const apiParams = {
118+
categories: filter.categories,
119+
...extraParams,
120+
keyword,
121+
isStar: filter.selectedStar ? true : undefined,
122+
type: getFirstOfArray(filter?.type) || undefined,
123+
status: getFirstOfArray(filter?.status) || undefined,
124+
built_in: filter?.builtIn !== undefined ? (getFirstOfArray(filter?.builtIn) === "true") : undefined,
125+
tags: filter?.tags?.length ? filter.tags.join(",") : undefined,
126+
page: current - pageOffset,
127+
size: pageSize, // Use camelCase for HTTP query params
128+
};
129+
130+
// 添加可能存在的额外参数(如 start_time, end_time 等)
131+
Object.keys(searchParams).forEach(key => {
132+
if (!['keyword', 'filter', 'current', 'pageSize'].includes(key) && apiParams[key as keyof typeof apiParams] === undefined) {
133+
(apiParams as any)[key] = (searchParams as any)[key];
134+
}
135+
});
136+
117137
const promises = [
118-
fetchFunc({
119-
categories: filter.categories,
120-
...extraParams,
121-
keyword,
122-
isStar: filter.selectedStar ? true : undefined,
123-
type: getFirstOfArray(filter?.type) || undefined,
124-
status: getFirstOfArray(filter?.status) || undefined,
125-
tags: filter?.tags?.length ? filter.tags.join(",") : undefined,
126-
page: current - pageOffset,
127-
size: pageSize, // Use camelCase for HTTP query params
128-
}),
138+
fetchFunc(apiParams),
129139
...additionalPollingFuncs.map((func) => func()),
130140
];
131141

frontend/src/i18n/locales/en/common.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2446,6 +2446,9 @@
24462446
},
24472447
"searchControls": {
24482448
"searchPlaceholder": "Search...",
2449+
"startTime": "Start Time",
2450+
"endTime": "End Time",
2451+
"selectedFilters": "Selected Filters:",
24492452
"dateRange": {
24502453
"start": "Start Date",
24512454
"end": "End Date",

frontend/src/i18n/locales/zh/common.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2446,6 +2446,9 @@
24462446
},
24472447
"searchControls": {
24482448
"searchPlaceholder": "搜索...",
2449+
"startTime": "开始时间",
2450+
"endTime": "结束时间",
2451+
"selectedFilters": "已选筛选:",
24492452
"dateRange": {
24502453
"start": "开始日期",
24512454
"end": "结束日期",

0 commit comments

Comments
 (0)