Add FilterDropdown component and column filter props factory
src/app/table-filters.tsx renders type-specific filter UIs (text search, category checkbox list, date range) and exposes buildColumnFilterProps to attach antd controlled filter props (filterDropdown/onFilter/filteredValue) to a column. Verified against antd v6.4.3 type defs. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
139
src/app/table-filters.tsx
Normal file
139
src/app/table-filters.tsx
Normal file
@@ -0,0 +1,139 @@
|
||||
"use client";
|
||||
|
||||
import { Button, Input, Checkbox, DatePicker, Space } from "antd";
|
||||
import dayjs from "dayjs";
|
||||
import type { ColumnType } from "antd/es/table";
|
||||
import type { FilterDropdownProps } from "antd/es/table/interface";
|
||||
import { inferFilterType, distinctValues, matchesFilter } from "./table-filters";
|
||||
import type { DataRow, FilterType } from "./table-filters";
|
||||
|
||||
const DATE_FMT = "YYYY-MM-DD";
|
||||
|
||||
/** 解析受控 selectedKeys 里的 "start|end" 编码为 RangePicker 值。 */
|
||||
function decodeRange(keys: React.Key[]): [dayjs.Dayjs | null, dayjs.Dayjs | null] {
|
||||
const raw = keys[0] != null ? String(keys[0]) : "";
|
||||
const sep = raw.indexOf("|");
|
||||
if (sep < 0) return [null, null];
|
||||
const s = raw.slice(0, sep);
|
||||
const e = raw.slice(sep + 1);
|
||||
return [s ? dayjs(s, DATE_FMT) : null, e ? dayjs(e, DATE_FMT) : null];
|
||||
}
|
||||
|
||||
export function FilterDropdown({
|
||||
type,
|
||||
distinct,
|
||||
setSelectedKeys,
|
||||
selectedKeys,
|
||||
confirm,
|
||||
clearFilters,
|
||||
}: FilterDropdownProps & {
|
||||
type: FilterType;
|
||||
distinct: string[];
|
||||
}): React.ReactElement {
|
||||
if (type === "date") {
|
||||
const [start, end] = decodeRange(selectedKeys);
|
||||
return (
|
||||
<div style={{ padding: 8 }}>
|
||||
<DatePicker.RangePicker
|
||||
value={[start, end]}
|
||||
onChange={(dates) => {
|
||||
if (!dates || dates[0] == null || dates[1] == null) {
|
||||
setSelectedKeys([]);
|
||||
} else {
|
||||
const s = dates[0].format(DATE_FMT);
|
||||
const e = dates[1].format(DATE_FMT);
|
||||
setSelectedKeys([`${s}|${e}`]); // 单个编码键,供 onFilter 解码
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Space style={{ marginTop: 8, display: "flex", justifyContent: "flex-end" }}>
|
||||
<Button
|
||||
size="small"
|
||||
onClick={() => {
|
||||
clearFilters?.();
|
||||
confirm();
|
||||
}}
|
||||
>
|
||||
重置
|
||||
</Button>
|
||||
<Button type="primary" size="small" onClick={() => confirm()}>
|
||||
确定
|
||||
</Button>
|
||||
</Space>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (type === "category") {
|
||||
return (
|
||||
<div style={{ padding: 8, maxWidth: 260 }}>
|
||||
<Checkbox.Group
|
||||
style={{ display: "flex", flexDirection: "column", maxHeight: 240, overflow: "auto" }}
|
||||
value={(selectedKeys as string[]) ?? []}
|
||||
onChange={(vals) => setSelectedKeys(vals as React.Key[])}
|
||||
options={distinct}
|
||||
/>
|
||||
<Space style={{ marginTop: 8, display: "flex", justifyContent: "flex-end" }}>
|
||||
<Button
|
||||
size="small"
|
||||
onClick={() => {
|
||||
clearFilters?.();
|
||||
confirm();
|
||||
}}
|
||||
>
|
||||
重置
|
||||
</Button>
|
||||
<Button type="primary" size="small" onClick={() => confirm()}>
|
||||
确定
|
||||
</Button>
|
||||
</Space>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// text
|
||||
const textValue = selectedKeys[0] != null ? String(selectedKeys[0]) : "";
|
||||
return (
|
||||
<div style={{ padding: 8 }}>
|
||||
<Input
|
||||
placeholder="输入关键字"
|
||||
value={textValue}
|
||||
onChange={(e) => setSelectedKeys(e.target.value ? [e.target.value] : [])}
|
||||
onPressEnter={() => confirm()}
|
||||
style={{ marginBottom: 8, display: "block" }}
|
||||
allowClear
|
||||
/>
|
||||
<Space style={{ display: "flex", justifyContent: "flex-end" }}>
|
||||
<Button
|
||||
size="small"
|
||||
onClick={() => {
|
||||
clearFilters?.();
|
||||
confirm();
|
||||
}}
|
||||
>
|
||||
重置
|
||||
</Button>
|
||||
<Button type="primary" size="small" onClick={() => confirm()}>
|
||||
确定
|
||||
</Button>
|
||||
</Space>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/** 为某列构造 antd 受控筛选属性(filterDropdown / onFilter / filteredValue)。 */
|
||||
export function buildColumnFilterProps(
|
||||
key: string,
|
||||
data: DataRow[],
|
||||
filteredValue: React.Key[] | null
|
||||
): Partial<ColumnType<DataRow>> {
|
||||
const type = inferFilterType(key, data);
|
||||
const distinct = type === "category" ? distinctValues(key, data) : [];
|
||||
return {
|
||||
filterDropdown: (props) => (
|
||||
<FilterDropdown type={type} distinct={distinct} {...props} />
|
||||
),
|
||||
onFilter: (value, record) => matchesFilter(type, record[key], String(value)),
|
||||
filteredValue: filteredValue ?? null,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user