From ba16156126471749d9122d6ab7349c965da13033 Mon Sep 17 00:00:00 2001 From: Lukas Harbarth Date: Tue, 6 May 2025 15:34:57 +0200 Subject: [PATCH] feat(AnalyticalTable): introduce `onFilter` callback prop --- .../AnalyticalTable/AnalyticalTable.cy.tsx | 26 +++++++++++++++++++ .../src/components/AnalyticalTable/index.tsx | 14 ++++++++-- .../tableReducer/stateReducer.ts | 11 ++++++-- .../components/AnalyticalTable/types/index.ts | 18 ++++++++++++- 4 files changed, 64 insertions(+), 5 deletions(-) diff --git a/packages/main/src/components/AnalyticalTable/AnalyticalTable.cy.tsx b/packages/main/src/components/AnalyticalTable/AnalyticalTable.cy.tsx index 86a53f920ef..206223280b9 100644 --- a/packages/main/src/components/AnalyticalTable/AnalyticalTable.cy.tsx +++ b/packages/main/src/components/AnalyticalTable/AnalyticalTable.cy.tsx @@ -468,6 +468,7 @@ describe('AnalyticalTable', () => { }); it('tree selection & filtering', () => { + const filterSpy = cy.spy().as('filter'); const TreeSelectFilterTable = (props: PropTypes) => { const [filter, setFilter] = useState(''); const [relevantPayload, setRelevantPayload] = useState>({}); @@ -501,6 +502,7 @@ describe('AnalyticalTable', () => { data={dataTree} globalFilterValue={filter} selectionMode="Multiple" + onFilter={filterSpy} />
{JSON.stringify(relevantPayload?.selectedFlatRows?.filter(Boolean).length)} @@ -553,6 +555,12 @@ describe('AnalyticalTable', () => { // column filter + select cy.findByText('Name').click(); cy.get(`[ui5-input][show-clear-icon]`).typeIntoUi5Input('Flowers Mcfarland', { force: true }); + cy.get('@filter').should('have.callCount', 17); + cy.get('@filter').should('have.been.calledWithMatch', { + value: 'Flowers Mcfarland', + columnId: 'name', + filters: [{ id: 'name', value: 'Flowers Mcfarland' }] + }); cy.findByText('Robin Moreno').should('not.exist', { timeout: 100 }); cy.findByText('Judith Mathews').should('not.exist', { timeout: 100 }); cy.findByText('Katy Bradshaw').should('not.exist', { timeout: 100 }); @@ -576,6 +584,8 @@ describe('AnalyticalTable', () => { } } ]; + + const filterSpy = cy.spy().as('filter'); const TestComp = ({ onRowSelect }: PropTypes) => { const [selectedRowIds, setSelectedRowIds] = useState({}); const [selectedFlatRows, setSelectedFlatRows] = useState([]); @@ -610,6 +620,7 @@ describe('AnalyticalTable', () => { setAllRowsSelected(e.detail.allRowsSelected); onRowSelect(e); }} + onFilter={filterSpy} selectionMode={AnalyticalTableSelectionMode.Multiple} selectedRowIds={selectedRowIds} /> @@ -696,6 +707,12 @@ describe('AnalyticalTable', () => { cy.findByText('Name-5').click(); cy.findByText('Name').click(); cy.get('[ui5-li-custom]').shadow().get('[ui5-input]').typeIntoUi5Input('7{enter}'); + cy.get('@filter').should('have.callCount', 1); + cy.get('@filter').should('have.been.calledWithMatch', { + value: '7', + columnId: 'name', + filters: [{ id: 'name', value: '7' }] + }); cy.findByTestId('payload').should('have.text', '["0","1","5"]'); cy.findByTestId('payloadRowsById').should('have.text', '{"0":true,"1":true,"5":true}'); cy.findByTestId('payloadAllRowsSelected').should('have.text', 'false'); @@ -708,10 +725,18 @@ describe('AnalyticalTable', () => { cy.findByText('Name').click(); cy.get('[ui5-li-custom]').shadow().get('[ui5-input]').typeIntoUi5Input('{selectall}{backspace}{enter}'); + cy.get('@filter').should('have.callCount', 2); + cy.get('@filter').should('have.been.calledWithMatch', { + value: undefined, + columnId: 'name', + filters: [] + }); + cy.get('[data-row-index="0"][data-column-index="0"]').click(); cy.findByText('Name-17').click({ force: true }); cy.findByText('Name').click(); cy.get('[ui5-li-custom]').shadow().get('[ui5-input]').typeIntoUi5Input('7{enter}'); + cy.get('@filter').should('have.callCount', 3); cy.findByTestId('payload').should( 'have.text', '["0","1","2","3","4","5","6","7","8","9","10","11","12","13","14","15","16","18","19","20"]' @@ -734,6 +759,7 @@ describe('AnalyticalTable', () => { cy.findByText('Name').click(); cy.get('[ui5-li-custom]').shadow().get('[ui5-input]').typeIntoUi5Input('{selectall}{backspace}{enter}'); + cy.get('@filter').should('have.callCount', 4); cy.findByText('Name-17').click({ force: true }); cy.findByTestId('input').type('7{enter}'); diff --git a/packages/main/src/components/AnalyticalTable/index.tsx b/packages/main/src/components/AnalyticalTable/index.tsx index 5fc7323ca4b..cea4e49e9da 100644 --- a/packages/main/src/components/AnalyticalTable/index.tsx +++ b/packages/main/src/components/AnalyticalTable/index.tsx @@ -159,6 +159,7 @@ const AnalyticalTable = forwardRef(null); const scrollContainerRef = useRef(null); + const dedupedOnFilter = useMemo(() => debounce(onFilter, 0), [onFilter]); + useEffect( + () => () => { + dedupedOnFilter.cancel(); + }, + [dedupedOnFilter] + ); + tableInstanceRef.current = useTable( { columns, @@ -238,8 +247,9 @@ const AnalyticalTable = forwardRef { +export const stateReducer = (state, action, _prevState, instance: TableInstance) => { const { payload } = action; - if (state.isRtl && action.type === actions.columnResizing) { const { clientX } = action; const { startX, columnWidth, headerIdWidths } = state.columnResizing; @@ -28,6 +28,13 @@ export const stateReducer = (state, action, _prevState, instance) => { }; } switch (action.type) { + case 'setFilter': + instance.webComponentsReactProperties.onFilter({ + filters: state.filters, + value: action.filterValue, + columnId: action.columnId + }); + return state; case 'toggleRowExpanded': // this flag disables scrolling to the top of the table if a table is collapsed if (!state.expanded[action.id]) { diff --git a/packages/main/src/components/AnalyticalTable/types/index.ts b/packages/main/src/components/AnalyticalTable/types/index.ts index a5b3a1f18e2..34f22101613 100644 --- a/packages/main/src/components/AnalyticalTable/types/index.ts +++ b/packages/main/src/components/AnalyticalTable/types/index.ts @@ -233,6 +233,7 @@ export interface WCRPropertiesType { uniqueId: string; subRowsKey: AnalyticalTablePropTypes['subRowsKey']; onColumnsReorder: AnalyticalTablePropTypes['onColumnsReorder']; + onFilter: AnalyticalTablePropTypes['onFilter']; } export interface RowType { @@ -280,7 +281,7 @@ export interface AnalyticalTableState { columnOrder: string[]; columnResizing: Record; expanded: Record; - filters: Record[]; + filters: Filter[]; groupBy: string[]; hiddenColumns: string[]; selectedRowIds: Record; @@ -300,6 +301,11 @@ export interface AnalyticalTableState { triggerScroll?: TriggerScrollState; } +interface Filter { + id: number | string; + value: string; +} + interface CellLabelParam { instance: Record; cell: Record; @@ -950,6 +956,10 @@ export interface AnalyticalTablePropTypes extends Omit { * __Note:__ Auto-resize is only available on columns that have the `autoResizable` option set to `true`. */ onAutoResize?: (e?: OnAutoResizeMouseEvent) => void; + /** + * Fired when a filter is applied to a column. + */ + onFilter?: (e: OnFilterParam) => void; // default components /** * Component that will be rendered when the table is not loading and has no data. @@ -969,6 +979,12 @@ export interface AnalyticalTablePropTypes extends Omit { tableInstance?: Ref; } +interface OnFilterParam { + filters: Filter[]; + value: string | undefined; + columnId: string | number; +} + interface ConfigParam { instance: TableInstance; }