Skip to content

Commit c406bb5

Browse files
committed
add basic filters in headers
1 parent 29f29b3 commit c406bb5

File tree

5 files changed

+59
-28
lines changed

5 files changed

+59
-28
lines changed

client/packages/lowcoder/src/comps/comps/tableLiteComp/nodes/dataNodes.ts

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,31 @@ export function buildSortedDataNode(comp: TableImplComp) {
7070
export function buildFilteredDataNode(comp: TableImplComp) {
7171
const nodes = {
7272
data: buildSortedDataNode(comp),
73+
// Add filter state node
74+
headerFilters: comp.children.headerFilters.node(),
7375
};
74-
const filteredDataNode = withFunction(fromRecord(nodes), ({ data }) => {
75-
// No pre-filtering here; AntD header filters are handled internally by Table
76-
return data.map((row) => tranToTableRecord(row, (row as any)[OB_ROW_ORI_INDEX]));
76+
const filteredDataNode = withFunction(fromRecord(nodes), ({ data, headerFilters }) => {
77+
let filteredData = data;
78+
79+
// Apply ANTD header filters if any exist
80+
if (headerFilters && Object.keys(headerFilters).length > 0) {
81+
filteredData = data.filter((record) => {
82+
return Object.entries(headerFilters).every(([columnKey, filterValues]) => {
83+
if (!filterValues || !Array.isArray(filterValues) || filterValues.length === 0) {
84+
return true; // No filter applied for this column
85+
}
86+
87+
const cellValue = record[columnKey];
88+
// Check if cell value matches any of the selected filter values
89+
return filterValues.some(filterValue => {
90+
if (cellValue == null) return filterValue == null;
91+
return String(cellValue) === String(filterValue);
92+
});
93+
});
94+
});
95+
}
96+
97+
return filteredData.map((row) => tranToTableRecord(row, (row as any)[OB_ROW_ORI_INDEX]));
7798
});
7899
return lastValueIfEqual(comp, "filteredDataNode", [filteredDataNode, nodes] as const, (a, b) =>
79100
shallowEqual(a[1], b[1])

client/packages/lowcoder/src/comps/comps/tableLiteComp/parts/ResizeableTable.tsx

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@ export type ResizeableTableProps<RecordType> = Omit<TableProps<RecordType>, "com
1616
rowAutoHeight?: boolean;
1717
customLoading?: boolean;
1818
onCellClick: (columnName: string, dataIndex: string) => void;
19-
virtualEnabled?: boolean;
20-
virtualBodyHeight?: number;
2119
};
2220

2321
/**
@@ -34,8 +32,6 @@ function ResizeableTableComp<RecordType extends object>(props: ResizeableTablePr
3432
rowAutoHeight,
3533
customLoading,
3634
onCellClick,
37-
virtualEnabled,
38-
virtualBodyHeight,
3935
...restProps
4036
} = props;
4137
const [resizeData, setResizeData] = useState({ index: -1, width: -1 });
@@ -111,14 +107,6 @@ function ResizeableTableComp<RecordType extends object>(props: ResizeableTablePr
111107
});
112108
}, [columns, resizeData, createCellHandler, createHeaderCellHandler]);
113109

114-
// Ensure scroll.x is preserved and inject scroll.y when virtualization is enabled
115-
const mergedScroll = useMemo(() => {
116-
const xScroll = { x: COL_MIN_WIDTH * columns.length } as any;
117-
const incoming = (restProps as any).scroll || {};
118-
const y = virtualEnabled ? (virtualBodyHeight ?? incoming.y ?? 480) : incoming.y;
119-
return { ...xScroll, ...incoming, ...(y ? { y } : {}) };
120-
}, [columns.length, restProps, virtualEnabled, virtualBodyHeight]);
121-
122110
return (
123111
<Table<RecordType>
124112
components={{
@@ -130,11 +118,8 @@ function ResizeableTableComp<RecordType extends object>(props: ResizeableTablePr
130118
},
131119
}}
132120
{...(restProps as any)}
133-
// Enable AntD virtual rendering when requested (AntD will ignore if unsupported)
134-
{...(virtualEnabled ? { virtual: true } : {})}
135121
pagination={false}
136122
columns={memoizedColumns}
137-
scroll={mergedScroll}
138123
/>
139124
);
140125
}

client/packages/lowcoder/src/comps/comps/tableLiteComp/tableCompView.tsx

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ export const TableCompView = React.memo((props: {
4242
[compChildren.dynamicColumnConfig]
4343
);
4444
const columnsAggrData = comp.columnAggrData;
45+
const headerFilters = useMemo(() => compChildren.headerFilters.getView(), [compChildren.headerFilters]);
4546
const antdColumns = useMemo(
4647
() =>
4748
columnsToAntdFormat(
@@ -53,6 +54,7 @@ export const TableCompView = React.memo((props: {
5354
dynamicColumnConfig,
5455
columnsAggrData,
5556
onEvent,
57+
headerFilters,
5658
),
5759
[
5860
columnViews,
@@ -62,6 +64,7 @@ export const TableCompView = React.memo((props: {
6264
dynamicColumn,
6365
dynamicColumnConfig,
6466
columnsAggrData,
67+
headerFilters,
6568
]
6669
);
6770

@@ -146,11 +149,6 @@ export const TableCompView = React.memo((props: {
146149
((showDataLoadingIndicators) && (compChildren.data as any).isLoading()) ||
147150
compChildren.loading.getView();
148151

149-
// Virtualization: keep pagination, virtualize within the visible body
150-
const virtualEnabled = true; // enable by default for lite table
151-
// Estimate a sensible body height. If table auto-height is enabled, fallback to 480.
152-
const virtualBodyHeight = 480;
153-
154152
return (
155153
<>
156154
{toolbar.position === "above" && !hideToolbar && toolbarView}
@@ -174,8 +172,6 @@ export const TableCompView = React.memo((props: {
174172
columnsStyle={columnsStyle}
175173
rowAutoHeight={compChildren.rowAutoHeight.getView()}
176174
customLoading={showTableLoading}
177-
virtualEnabled={virtualEnabled}
178-
virtualBodyHeight={virtualBodyHeight}
179175
onCellClick={(columnName: string, dataIndex: string) => {
180176
comp.children.selectedCell.dispatchChangeValueAction({
181177
name: columnName,

client/packages/lowcoder/src/comps/comps/tableLiteComp/tableTypes.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,8 @@ const tableChildrenMap = {
194194
selection: SelectionControl,
195195
pagination: PaginationControl,
196196
sort: valueComp<Array<SortValue>>([]),
197+
// Header filters state for ANTD column header filters
198+
headerFilters: stateComp<Record<string, any[]>>({}),
197199
toolbar: TableToolbarComp,
198200
showSummary: BoolControl,
199201
summaryRows: dropdownControl(summarRowsOptions, "1"),

client/packages/lowcoder/src/comps/comps/tableLiteComp/tableUtils.tsx

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -373,17 +373,33 @@ function buildFilterProps(
373373
filterable: boolean,
374374
candidateTags: any[],
375375
uniqueValues: any[],
376+
headerFilters: Record<string, any[]> = {},
376377
) {
377378
if (!filterable) return {};
379+
378380
const candidates = (Array.isArray(candidateTags) && candidateTags.length > 0
379381
? candidateTags
380382
: Array.isArray(uniqueValues) && uniqueValues.length > 0
381383
? uniqueValues
382384
: [])
383385
.slice(0, 100);
386+
387+
if (candidates.length === 0) return {};
388+
384389
return {
385-
filters: candidates.map((v) => ({ text: String(v), value: v })),
386-
onFilter: (value: any, record: any) => String(record[dataIndex] ?? "") === String(value ?? ""),
390+
filters: candidates.map((v) => ({
391+
text: String(v ?? 'null'),
392+
value: v
393+
})),
394+
filteredValue: headerFilters[dataIndex] || null,
395+
// Enable search within the filter dropdown (AntD)
396+
filterSearch: true,
397+
// Use tree mode for better UX on long lists (AntD)
398+
filterMode: 'tree',
399+
// Allow selecting multiple values per column by default
400+
filterMultiple: true,
401+
// Remove onFilter as we handle filtering in buildFilteredDataNode
402+
// ANTD will call onChange with filters parameter instead
387403
} as const;
388404
}
389405

@@ -489,6 +505,7 @@ export function columnsToAntdFormat(
489505
dynamicColumnConfig: Array<string>,
490506
columnsAggrData: ColumnsAggrData,
491507
onTableEvent: (eventName: any) => void,
508+
headerFilters: Record<string, any[]> = {},
492509
): Array<CustomColumnType<RecordType>> {
493510
const customColumns = columns.filter(col => col.isCustom).map(col => col.dataIndex);
494511
const initialColumns = getInitialColumns(columnsAggrData, customColumns);
@@ -504,7 +521,7 @@ export function columnsToAntdFormat(
504521

505522
const { candidateTags, candidateStatus, uniqueValues } = extractAggrForColumn(column.dataIndex, columnsAggrData);
506523
const title = renderTitle({ title: column.title, tooltip: column.titleTooltip });
507-
const filterProps = buildFilterProps(column.dataIndex, column.filterable, candidateTags, uniqueValues);
524+
const filterProps = buildFilterProps(column.dataIndex, column.filterable, candidateTags, uniqueValues, headerFilters);
508525
const { style, linkStyle } = buildStyleProps(column);
509526
const multiplePriority = (sortedColumns.length - mIndex) + 1;
510527
const sorterProps = buildSorterProps(column, sortMap.get(column.dataIndex), multiplePriority);
@@ -572,6 +589,16 @@ export function onTableChange(
572589
onEvent("sortChange");
573590
}
574591
if (extra.action === "filter") {
592+
// Convert filters to a format suitable for our filter state
593+
const headerFilters: Record<string, any[]> = {};
594+
Object.entries(filters).forEach(([columnKey, filterValues]) => {
595+
if (filterValues && Array.isArray(filterValues) && filterValues.length > 0) {
596+
headerFilters[columnKey] = filterValues;
597+
}
598+
});
599+
600+
// Dispatch action to update header filters state
601+
dispatch(changeChildAction("headerFilters", headerFilters, true));
575602
onEvent("filterChange");
576603
}
577604
}

0 commit comments

Comments
 (0)