Skip to content

Commit 28c86f0

Browse files
committed
[Feat]: Add table column filters
1 parent baf0221 commit 28c86f0

File tree

7 files changed

+106
-3
lines changed

7 files changed

+106
-3
lines changed

client/packages/lowcoder/src/comps/comps/tableComp/column/tableColumnComp.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ export const columnChildrenMap = {
128128
dataIndex: valueComp<string>(""),
129129
hide: BoolControl,
130130
sortable: BoolControl,
131+
filterable: withDefault(BoolControl, false),
131132
width: NumberControl,
132133
autoWidth: dropdownControl(columnWidthOptions, "auto"),
133134
render: RenderComp,
@@ -270,6 +271,9 @@ const ColumnPropertyView = React.memo(({
270271
{comp.children.sortable.propertyView({
271272
label: trans("table.sortable"),
272273
})}
274+
{comp.children.filterable.propertyView({
275+
label: trans("table.filterable"),
276+
})}
273277
{comp.children.hide.propertyView({
274278
label: trans("prop.hide"),
275279
})}

client/packages/lowcoder/src/comps/comps/tableComp/tableComp.test.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,10 @@ test("test table data transform", () => {
186186
)
187187
);
188188
getAndExpectTableData(2, comp);
189+
comp = evalAndReduce(comp.reduce(comp.changeChildAction("headerFilters", { name: ["gg2"] })));
190+
({ transformedData } = getAndExpectTableData(1, comp));
191+
expect(transformedData.map((d: any) => d["name"])).toEqual(["gg2"]);
192+
comp = evalAndReduce(comp.reduce(comp.changeChildAction("headerFilters", {})));
189193
// filter
190194
comp = evalAndReduce(
191195
comp.reduce(

client/packages/lowcoder/src/comps/comps/tableComp/tableComp.tsx

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { getPageSize } from "comps/comps/tableComp/paginationControl";
33
import { EMPTY_ROW_KEY, TableCompView } from "comps/comps/tableComp/tableCompView";
44
import { TableFilter } from "comps/comps/tableComp/tableToolbarComp";
55
import {
6+
applyHeaderFilters,
67
columnHide,
78
ColumnsAggrData,
89
COLUMN_CHILDREN_KEY,
@@ -357,12 +358,14 @@ export class TableImplComp extends TableInitComp implements IContainer {
357358
data: this.sortDataNode(),
358359
searchValue: this.children.searchText.node(),
359360
filter: this.children.toolbar.children.filter.node(),
361+
headerFilters: this.children.headerFilters.node(),
360362
showFilter: this.children.toolbar.children.showFilter.node(),
361363
};
362364
let context = this;
363365
const filteredDataNode = withFunction(fromRecord(nodes), (input) => {
364-
const { data, searchValue, filter, showFilter } = input;
365-
const filteredData = filterData(data, searchValue.value, filter, showFilter.value);
366+
const { data, searchValue, filter, headerFilters, showFilter } = input;
367+
const toolbarFilteredData = filterData(data, searchValue.value, filter, showFilter.value);
368+
const filteredData = applyHeaderFilters(toolbarFilteredData, headerFilters);
366369
// console.info("filterNode. data: ", data, " filter: ", filter, " filteredData: ", filteredData);
367370
// if data is changed on search then trigger event
368371
if(Boolean(searchValue.value) && data.length !== filteredData.length) {
@@ -1141,6 +1144,18 @@ export const TableComp = withExposingConfigs(TableTmpComp, [
11411144
},
11421145
trans("table.filterDesc")
11431146
),
1147+
new DepsConfig(
1148+
"headerFilters",
1149+
(children) => {
1150+
return {
1151+
headerFilters: children.headerFilters.node(),
1152+
};
1153+
},
1154+
(input) => {
1155+
return input.headerFilters;
1156+
},
1157+
trans("table.headerFiltersDesc")
1158+
),
11441159
new DepsConfig(
11451160
"selectedCell",
11461161
(children) => {

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ export const TableCompView = React.memo((props: {
108108
() => compChildren.dynamicColumnConfig.getView(),
109109
[compChildren.dynamicColumnConfig]
110110
);
111+
const headerFilters = useMemo(() => compChildren.headerFilters.getView(), [compChildren.headerFilters]);
111112
const columnsAggrData = comp.columnAggrData;
112113
const expansion = useMemo(() => compChildren.expansion.getView(), [compChildren.expansion]);
113114
const antdColumns = useMemo(
@@ -122,6 +123,7 @@ export const TableCompView = React.memo((props: {
122123
columnsAggrData,
123124
editModeClicks,
124125
onEvent,
126+
headerFilters,
125127
),
126128
[
127129
columnViews,
@@ -132,6 +134,7 @@ export const TableCompView = React.memo((props: {
132134
dynamicColumnConfig,
133135
columnsAggrData,
134136
editModeClicks,
137+
headerFilters,
135138
]
136139
);
137140

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,7 @@ const tableChildrenMap = {
242242
selection: SelectionControl,
243243
pagination: PaginationControl,
244244
sort: valueComp<Array<SortValue>>([]),
245+
headerFilters: stateComp<Record<string, any[]>>({}),
245246
toolbar: TableToolbarComp,
246247
showSummary: BoolControl,
247248
summaryRows: dropdownControl(summarRowsOptions, "1"),

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

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,31 @@ export function filterData(
9999
return resultData;
100100
}
101101

102+
export function applyHeaderFilters(
103+
data: Array<RecordType>,
104+
headerFilters: Record<string, any[]>
105+
) {
106+
if (!headerFilters || Object.keys(headerFilters).length === 0) {
107+
return data;
108+
}
109+
110+
return data.filter((row) =>
111+
Object.entries(headerFilters).every(([columnKey, filterValues]) => {
112+
if (!Array.isArray(filterValues) || filterValues.length === 0) {
113+
return true;
114+
}
115+
116+
const cellValue = row[columnKey];
117+
return filterValues.some((filterValue) => {
118+
if (cellValue == null) {
119+
return filterValue == null;
120+
}
121+
return String(cellValue) === String(filterValue);
122+
});
123+
})
124+
);
125+
}
126+
102127
export function sortData(
103128
data: Array<JSONObject>,
104129
columns: Record<string, { sortable: boolean }>, // key: dataIndex
@@ -291,6 +316,14 @@ export function getColumnsAggr(
291316
.uniqBy("text")
292317
.value();
293318
}
319+
320+
res.uniqueValues = _(oriDisplayData)
321+
.map((row) => row[dataIndex])
322+
.filter((value): value is JSONValue => value !== undefined && value !== null && value !== "")
323+
.uniqWith(_.isEqual)
324+
.slice(0, 100)
325+
.value();
326+
294327
return res;
295328
});
296329
}
@@ -338,6 +371,27 @@ export type CustomColumnType<RecordType> = ColumnType<RecordType> & {
338371
columnDataTestId?: string;
339372
};
340373

374+
function buildHeaderFilterProps(
375+
dataIndex: string,
376+
filterable: boolean,
377+
uniqueValues: any[],
378+
headerFilters: Record<string, any[]> = {}
379+
) {
380+
if (!filterable || uniqueValues.length === 0) {
381+
return {};
382+
}
383+
384+
return {
385+
filters: uniqueValues.map((value) => ({
386+
text: String(value),
387+
value,
388+
})),
389+
filteredValue: headerFilters[dataIndex] ?? null,
390+
filterSearch: true,
391+
filterMultiple: true,
392+
} as const;
393+
}
394+
341395
/**
342396
* convert column in raw format into antd format
343397
*/
@@ -351,6 +405,7 @@ export function columnsToAntdFormat(
351405
columnsAggrData: ColumnsAggrData,
352406
editMode: string,
353407
onTableEvent: (eventName: any) => void,
408+
headerFilters: Record<string, any[]> = {},
354409
): Array<CustomColumnType<RecordType>> {
355410
const customColumns = columns.filter(col => col.isCustom).map(col => col.dataIndex);
356411
const initialColumns = getInitialColumns(columnsAggrData, customColumns);
@@ -393,10 +448,18 @@ export function columnsToAntdFormat(
393448
text: string;
394449
status: StatusType;
395450
}[];
451+
const uniqueValues = ((columnsAggrData[column.dataIndex] ?? {}).uniqueValues ?? []) as any[];
452+
const columnKey = column.dataIndex || `custom-${mIndex}`;
396453
const title = renderTitle({ title: column.title, tooltip: column.titleTooltip, editable: column.editable });
454+
const filterProps = buildHeaderFilterProps(
455+
column.dataIndex,
456+
column.filterable,
457+
uniqueValues,
458+
headerFilters
459+
);
397460

398461
return {
399-
key: `${column.dataIndex}-${mIndex}`,
462+
key: columnKey,
400463
title: column.showTitle ? title : '',
401464
titleText: column.title,
402465
dataIndex: column.dataIndex,
@@ -468,6 +531,7 @@ export function columnsToAntdFormat(
468531
showSorterTooltip: false,
469532
}
470533
: {}),
534+
...filterProps,
471535
};
472536
});
473537
}
@@ -504,6 +568,16 @@ export function onTableChange(
504568
dispatch(changeChildAction("sort", sortValues, true));
505569
onEvent("sortChange");
506570
}
571+
572+
if (extra.action === "filter") {
573+
const headerFilters = _(filters)
574+
.pickBy((filterValues) => Array.isArray(filterValues) && filterValues.length > 0)
575+
.mapValues((filterValues) => filterValues as any[])
576+
.value();
577+
578+
dispatch(changeChildAction("headerFilters", headerFilters, true));
579+
onEvent("filterChange");
580+
}
507581
}
508582

509583
export function calcColumnWidth(columnKey: string, data: Array<JSONObject>) {

client/packages/lowcoder/src/i18n/locales/en.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2095,6 +2095,7 @@ export const en = {
20952095
"showTitle": "Show Title",
20962096
"showTitleTooltip": "Show/Hide column title in table header",
20972097
"sortable": "Sortable",
2098+
"filterable": "Filterable",
20982099
"align": "Alignment",
20992100
"fixedColumn": "Fixed Column",
21002101
"autoWidth": "Auto Width",
@@ -2170,6 +2171,7 @@ export const en = {
21702171
"displayDataDesc": "Data Displayed in the Current Table",
21712172
"selectedIndexDesc": "Selected Index in Display Data",
21722173
"filterDesc": "Table Filtering Parameters",
2174+
"headerFiltersDesc": "Header filter selections keyed by column name",
21732175
"dataDesc": "The JSON Data for the Table",
21742176
"saveChanges": "Save Changes",
21752177
"cancelChanges": "Cancel Changes",

0 commit comments

Comments
 (0)