From 18b01275f1964f6b94d987f4bc9a5a318fa27452 Mon Sep 17 00:00:00 2001 From: markuczy <martin.kuczynski@capgemini.com> Date: Tue, 5 Nov 2024 08:42:22 +0100 Subject: [PATCH] feat: filter view further improvements --- libs/angular-accelerator/assets/i18n/de.json | 8 ++- libs/angular-accelerator/assets/i18n/en.json | 8 ++- .../data-table/data-table.component.html | 2 + .../data-table/data-table.component.ts | 1 + .../filter-view/filter-view.component.html | 40 +++++++++++--- .../filter-view/filter-view.component.spec.ts | 54 ++++++++++++++++--- .../filter-view/filter-view.component.ts | 10 ++-- .../interactive-data-view.component.html | 4 +- ...interactive-data-view.component.stories.ts | 19 ++----- .../interactive-data-view.component.ts | 5 +- .../testing/filter-view.harness.ts | 9 ++-- .../assets/i18n/de.json | 8 ++- .../assets/i18n/en.json | 7 ++- 13 files changed, 127 insertions(+), 48 deletions(-) diff --git a/libs/angular-accelerator/assets/i18n/de.json b/libs/angular-accelerator/assets/i18n/de.json index 1ee777262..1c9bb4faa 100644 --- a/libs/angular-accelerator/assets/i18n/de.json +++ b/libs/angular-accelerator/assets/i18n/de.json @@ -114,8 +114,12 @@ }, "TABLE": { "COLUMN_NAME": "Spaltenname", - "VALUE": "Filterwert" - } + "VALUE": "Filterwert", + "ACTIONS": "Aktionen", + "REMOVE_FILTER_TITLE": "Filter löschen", + "REMOVE_FILTER_ARIA_LABEL": "Filter löschen" + }, + "PANEL_TITLE": "Filter" }, "OCX_SEARCH_HEADER": { "TOGGLE_BUTTON": { diff --git a/libs/angular-accelerator/assets/i18n/en.json b/libs/angular-accelerator/assets/i18n/en.json index 211db983e..4ff99916c 100644 --- a/libs/angular-accelerator/assets/i18n/en.json +++ b/libs/angular-accelerator/assets/i18n/en.json @@ -114,8 +114,12 @@ }, "TABLE": { "COLUMN_NAME": "Column name", - "VALUE": "Filter value" - } + "VALUE": "Filter value", + "ACTIONS": "Actions", + "REMOVE_FILTER_TITLE": "Remove filter", + "REMOVE_FILTER_ARIA_LABEL": "Remove filter" + }, + "PANEL_TITLE": "Filters" }, "OCX_SEARCH_HEADER": { "TOGGLE_BUTTON": { diff --git a/libs/angular-accelerator/src/lib/components/data-table/data-table.component.html b/libs/angular-accelerator/src/lib/components/data-table/data-table.component.html index df7dc0fc9..21b584e0f 100644 --- a/libs/angular-accelerator/src/lib/components/data-table/data-table.component.html +++ b/libs/angular-accelerator/src/lib/components/data-table/data-table.component.html @@ -119,6 +119,8 @@ (selectionChange)="onSelectionChange($event)" [selection]="(selectedRows$ | async) ?? []" [scrollable]="true" + scrollHeight="flex" + [style]="tableStyle" paginatorDropdownAppendTo="body" > <ng-template pTemplate="header"> diff --git a/libs/angular-accelerator/src/lib/components/data-table/data-table.component.ts b/libs/angular-accelerator/src/lib/components/data-table/data-table.component.ts index 94b0eaed0..db2e4cc83 100644 --- a/libs/angular-accelerator/src/lib/components/data-table/data-table.component.ts +++ b/libs/angular-accelerator/src/lib/components/data-table/data-table.component.ts @@ -176,6 +176,7 @@ export class DataTableComponent extends DataSortBase implements OnInit, AfterCon @Input() allowSelectAll = true @Input() paginator = true @Input() page = 0 + @Input() tableStyle: { [klass: string]: any } | undefined @Input() get totalRecordsOnServer(): number | undefined { return this.params['totalRecordsOnServer'] ? Number(this.params['totalRecordsOnServer']) : undefined diff --git a/libs/angular-accelerator/src/lib/components/filter-view/filter-view.component.html b/libs/angular-accelerator/src/lib/components/filter-view/filter-view.component.html index 2fd864d2a..7b41f41ff 100644 --- a/libs/angular-accelerator/src/lib/components/filter-view/filter-view.component.html +++ b/libs/angular-accelerator/src/lib/components/filter-view/filter-view.component.html @@ -2,7 +2,7 @@ <ng-container *ngIf="showChips; else noChipsContent"> <ng-container *ocxIfBreakpoint="'desktop'; elseTemplate: noChipsContent"> <p-button - id="resetFiltersButton" + id="ocxFilterViewReset" (onClick)="onResetFilersClick()" icon="pi pi-eraser" pTooltip="{{ 'OCX_FILTER_VIEW.RESET_FILTERS_BUTTON.DETAIL' | translate }}" @@ -14,7 +14,7 @@ <ng-container *ngIf="_fitlerViewNoSelection; else defaultNoFilters" [ngTemplateOutlet]="_fitlerViewNoSelection"> </ng-container> <ng-template #defaultNoFilters> - <span id="noFiltersMessage">{{ 'OCX_FILTER_VIEW.NO_FILTERS' | translate }}</span> + <span id="ocxFilterViewNoFilters">{{ 'OCX_FILTER_VIEW.NO_FILTERS' | translate }}</span> </ng-template> </ng-container> <ng-container *ngIf="(chipTemplates$ | async) ?? {} as templates"> @@ -78,7 +78,7 @@ </ng-container> <ng-template #noChipsContent> <p-button - id="manageFiltersButton" + id="ocxFilterViewManage" (onClick)="showPanel($event)" icon="pi pi-sliders-h" label="{{ 'OCX_FILTER_VIEW.MANAGE_FILTERS_BUTTON.LABEL' | translate }}" @@ -92,16 +92,28 @@ </ng-template> <ng-template #filterTablePanel> - <p-overlayPanel *ngIf="tableTemplates$ | async as templates" #op [showCloseIcon]="true"> + <p-overlayPanel *ngIf="tableTemplates$ | async as templates" #op [showCloseIcon]="true" [style]="panelStyle"> <ng-template pTemplate="content"> + <div class="flex justify-content-between align-items-center mb-2"> + <span class="text-2xl font-medium">Filters</span> + <div> + <p-button + id="ocxFilterViewOverlayReset" + (onClick)="onResetFilersClick()" + icon="pi pi-eraser" + pTooltip="{{ 'OCX_FILTER_VIEW.RESET_FILTERS_BUTTON.DETAIL' | translate }}" + tooltipPosition="top" + tooltipEvent="hover" + [ariaLabel]="'OCX_FILTER_VIEW.RESET_FILTERS_BUTTON.ARIA_LABEL' | translate" + ></p-button> + </div> + </div> <ocx-data-table [rows]="(columnFilterDataRows$ | async) ?? []" [columns]="columnFilterTableColumns" [emptyResultsMessage]="'OCX_FILTER_VIEW.NO_FILTERS' | translate" - [actionColumnPosition]="'right'" - [paginator]="paginator" - [pageSize]="pageSize" - [pageSizes]="pageSizes" + [paginator]="false" + [tableStyle]="tableStyle" > <ng-template pTemplate="columnIdCell" let-rowObject="rowObject" let-column="column"> <ng-container @@ -125,6 +137,18 @@ > </ng-container> </ng-template> + <ng-template pTemplate="actionsIdCell" let-rowObject="rowObject" let-column="column"> + <div> + <button + pButton + class="p-button-rounded p-button-danger p-button-text" + title="{{ 'OCX_FILTER_VIEW.TABLE.REMOVE_FILTER_TITLE' | translate }}" + [attr.aria-label]="'OCX_FILTER_VIEW.TABLE.REMOVE_FILTER_ARIA_LABEL' | translate" + icon="pi pi-trash" + (click)="onFilterDelete(rowObject)" + ></button> + </div> + </ng-template> </ocx-data-table> </ng-template> </p-overlayPanel> diff --git a/libs/angular-accelerator/src/lib/components/filter-view/filter-view.component.spec.ts b/libs/angular-accelerator/src/lib/components/filter-view/filter-view.component.spec.ts index 40bfd92b6..9541decf7 100644 --- a/libs/angular-accelerator/src/lib/components/filter-view/filter-view.component.spec.ts +++ b/libs/angular-accelerator/src/lib/components/filter-view/filter-view.component.spec.ts @@ -296,7 +296,7 @@ describe('FilterViewComponent', () => { }) }) - describe('data table', () => { + describe('overlay', () => { it('should show data table with column filters', async () => { const datePipe = new DatePipe('en') let dataTable = await filterView.getDataTable() @@ -310,16 +310,58 @@ describe('FilterViewComponent', () => { expect(dataTable).toBeTruthy() const headers = await dataTable?.getHeaderColumns() expect(headers).toBeTruthy() - expect(headers?.length).toBe(2) + expect(headers?.length).toBe(3) expect(await headers![0].getText()).toBe('Column name') expect(await headers![1].getText()).toBe('Filter value') + expect(await headers![2].getText()).toBe('Actions') const rows = await dataTable?.getRows() expect(rows?.length).toBe(4) - expect(await rows![0].getData()).toEqual(['Name', 'name-filter']) - expect(await rows![1].getData()).toEqual(['Start date', datePipe.transform(deafultDate, 'medium')]) - expect(await rows![2].getData()).toEqual(['Status', 'My status']) - expect(await rows![3].getData()).toEqual(['Test number', 'Yes']) + expect(await rows![0].getData()).toEqual(['Name', 'name-filter', '']) + expect(await rows![1].getData()).toEqual(['Start date', datePipe.transform(deafultDate, 'medium'), '']) + expect(await rows![2].getData()).toEqual(['Status', 'My status', '']) + expect(await rows![3].getData()).toEqual(['Test number', 'Yes', '']) + }) + + it('should show reset all filters button above the table', async () => { + const filtersButton = await filterView.getFiltersButton() + await filtersButton?.click() + fixture.detectChanges() + + const resetButton = await filterView.getOverlayResetFiltersButton() + expect(resetButton).toBeTruthy() + const dataTable = await filterView.getDataTable() + expect((await dataTable?.getRows())?.length).toBe(4) + + await resetButton?.click() + const rows = await dataTable?.getRows() + expect(rows?.length).toBe(1) + expect(await rows![0].getData()).toEqual(['No filters selected']) + }) + + it('should show remove filter in action column', async () => { + const filteredEmitterSpy = jest.spyOn(component.filtered, 'emit') + const componentStateEmitterSpy = jest.spyOn(component.componentStateChanged, 'emit') + + const filtersButton = await filterView.getFiltersButton() + await filtersButton?.click() + fixture.detectChanges() + + const dataTable = await filterView.getDataTable() + let rows = await dataTable?.getRows() + expect(rows?.length).toBe(4) + const buttons = await rows![0].getAllActionButtons() + expect(buttons.length).toBe(1) + await buttons[0].click() + + rows = await dataTable?.getRows() + expect(rows?.length).toBe(3) + expect(component.filters.length).toBe(3) + const expectedFilters = mockFilters.filter((f) => f.columnId !== 'name') + expect(filteredEmitterSpy).toHaveBeenCalledWith(expectedFilters) + expect(componentStateEmitterSpy).toHaveBeenCalledWith({ + filters: expectedFilters, + }) }) }) }) diff --git a/libs/angular-accelerator/src/lib/components/filter-view/filter-view.component.ts b/libs/angular-accelerator/src/lib/components/filter-view/filter-view.component.ts index bcc52f7a6..46ac71458 100644 --- a/libs/angular-accelerator/src/lib/components/filter-view/filter-view.component.ts +++ b/libs/angular-accelerator/src/lib/components/filter-view/filter-view.component.ts @@ -65,9 +65,8 @@ export class FilterViewComponent implements OnInit { @Input() selectDisplayedChips: (filters: ColumnFilterData[]) => ColumnFilterData[] = (filters) => limit(filters, 3, { reverse: true }) @Input() chipStyleClass: string = '' - @Input() pageSize: number | undefined - @Input() pageSizes: number[] = [5, 10, 25] - @Input() paginator: boolean = true + @Input() tableStyle: { [klass: string]: any } = { 'max-height': '50vh' } + @Input() panelStyle: { [klass: string]: any } = { 'max-width': '90%' } @Output() filtered: EventEmitter<Filter[]> = new EventEmitter() @Output() componentStateChanged: EventEmitter<FilterViewComponentState> = new EventEmitter() @@ -84,6 +83,11 @@ export class FilterViewComponent implements OnInit { filterable: true, }, { id: 'value', columnType: ColumnType.STRING, nameKey: 'OCX_FILTER_VIEW.TABLE.VALUE' }, + { + id: 'actions', + columnType: ColumnType.STRING, + nameKey: 'OCX_FILTER_VIEW.TABLE.ACTIONS', + }, ] @ViewChild(OverlayPanel) panel!: OverlayPanel diff --git a/libs/angular-accelerator/src/lib/components/interactive-data-view/interactive-data-view.component.html b/libs/angular-accelerator/src/lib/components/interactive-data-view/interactive-data-view.component.html index 442f456da..6738bc79c 100644 --- a/libs/angular-accelerator/src/lib/components/interactive-data-view/interactive-data-view.component.html +++ b/libs/angular-accelerator/src/lib/components/interactive-data-view/interactive-data-view.component.html @@ -15,9 +15,7 @@ [showChips]="showFilterViewChips" [selectDisplayedChips]="selectDisplayedChips" [chipStyleClass]="filterViewChipStyleClass" - [pageSize]="filterViewPageSize" - [pageSizes]="filterViewPageSizes" - [paginator]="filterViewPaginator" + [tableStyle]="filterViewTableStyle" (filtered)="filtering($event)" (componentStateChanged)="filterViewComponentState$.next($event)" ></ocx-filter-view> diff --git a/libs/angular-accelerator/src/lib/components/interactive-data-view/interactive-data-view.component.stories.ts b/libs/angular-accelerator/src/lib/components/interactive-data-view/interactive-data-view.component.stories.ts index e0ed37154..6137c1de4 100644 --- a/libs/angular-accelerator/src/lib/components/interactive-data-view/interactive-data-view.component.stories.ts +++ b/libs/angular-accelerator/src/lib/components/interactive-data-view/interactive-data-view.component.stories.ts @@ -37,18 +37,10 @@ import { SlotService } from '@onecx/angular-remote-components' import { of } from 'rxjs' import { ColumnFilterData, FilterType } from '../../model/filter.model' import { FilterViewComponent } from '../filter-view/filter-view.component' -import { limit } from '../../utils/filter.utils' type InteractiveDataViewInputTypes = Pick< InteractiveDataViewComponent, - | 'data' - | 'columns' - | 'emptyResultsMessage' - | 'disableFilterView' - | 'showFilterViewChips' - | 'selectDisplayedChips' - | 'filterViewPageSize' - | 'filterViewPageSizes' + 'data' | 'columns' | 'emptyResultsMessage' | 'disableFilterView' | 'showFilterViewChips' > const InteractiveDataViewComponentSBConfig: Meta<InteractiveDataViewComponent> = { title: 'InteractiveDataViewComponent', @@ -184,9 +176,6 @@ const defaultComponentArgs: InteractiveDataViewInputTypes = { emptyResultsMessage: 'No results', disableFilterView: true, showFilterViewChips: false, - selectDisplayedChips: (columnFilterData) => limit(columnFilterData, 1, { reverse: true }), - filterViewPageSize: undefined, - filterViewPageSizes: [5, 10, 25], } export const WithMockData = { @@ -566,14 +555,14 @@ export const WithFilterViewCustomChipSelection = { }, } -export const WithFilterViewCustomTable = { +export const WithFilterViewCustomStyles = { render: Template, args: { ...defaultComponentArgs, disableFilterView: false, showFilterViewChips: false, - filterViewPageSize: 2, - filterViewPageSizes: [2, 4, 6], + filterViewTableStyle: { 'max-height': '30vh' }, + filterViewPanelStyle: { 'max-width': '80%' }, }, } diff --git a/libs/angular-accelerator/src/lib/components/interactive-data-view/interactive-data-view.component.ts b/libs/angular-accelerator/src/lib/components/interactive-data-view/interactive-data-view.component.ts index a2a000ac0..8981477d8 100644 --- a/libs/angular-accelerator/src/lib/components/interactive-data-view/interactive-data-view.component.ts +++ b/libs/angular-accelerator/src/lib/components/interactive-data-view/interactive-data-view.component.ts @@ -123,9 +123,8 @@ export class InteractiveDataViewComponent implements OnInit, AfterContentInit { @Input() disableFilterView = true @Input() showFilterViewChips = false @Input() filterViewChipStyleClass: string = '' - @Input() filterViewPageSize: number | undefined - @Input() filterViewPageSizes: number[] = [5, 10, 25] - @Input() filterViewPaginator: boolean = true + @Input() filterViewTableStyle: { [klass: string]: any } = { 'max-height': '50vh' } + @Input() filterViewPanelStyle: { [klass: string]: any } = { 'max-width': '90%' } @Input() selectDisplayedChips: (filters: ColumnFilterData[]) => ColumnFilterData[] = (filters) => limit(filters, 3, { reverse: true }) @Input() page = 0 diff --git a/libs/angular-accelerator/testing/filter-view.harness.ts b/libs/angular-accelerator/testing/filter-view.harness.ts index 6358624f3..ffdf41c3e 100644 --- a/libs/angular-accelerator/testing/filter-view.harness.ts +++ b/libs/angular-accelerator/testing/filter-view.harness.ts @@ -6,8 +6,11 @@ export class FilterViewHarness extends ContentContainerComponentHarness { static hostSelector = 'ocx-filter-view' getDataTable = this.documentRootLocatorFactory().locatorForOptional(DataTableHarness) - getFiltersButton = this.locatorForOptional(PButtonHarness.with({ id: 'manageFiltersButton' })) - getChipsResetFiltersButton = this.locatorForOptional(PButtonHarness.with({ id: 'resetFiltersButton' })) + getOverlayResetFiltersButton = this.documentRootLocatorFactory().locatorForOptional( + PButtonHarness.with({ id: 'ocxFilterViewOverlayReset' }) + ) + getFiltersButton = this.locatorForOptional(PButtonHarness.with({ id: 'ocxFilterViewManage' })) + getChipsResetFiltersButton = this.locatorForOptional(PButtonHarness.with({ id: 'ocxFilterViewReset' })) getChips = this.locatorForAll(PChipHarness) - getNoFiltersMessage = this.locatorForOptional(SpanHarness.with({ id: 'noFiltersMessage' })) + getNoFiltersMessage = this.locatorForOptional(SpanHarness.with({ id: 'ocxFilterViewNoFilters' })) } diff --git a/libs/portal-integration-angular/assets/i18n/de.json b/libs/portal-integration-angular/assets/i18n/de.json index 55fb85ba8..2ccad92d2 100644 --- a/libs/portal-integration-angular/assets/i18n/de.json +++ b/libs/portal-integration-angular/assets/i18n/de.json @@ -135,8 +135,12 @@ }, "TABLE": { "COLUMN_NAME": "Spaltenname", - "VALUE": "Filterwert" - } + "VALUE": "Filterwert", + "ACTIONS": "Aktionen", + "REMOVE_FILTER_TITLE": "Filter löschen", + "REMOVE_FILTER_ARIA_LABEL": "Filter löschen" + }, + "PANEL_TITLE": "Filter" }, "OCX_SEARCH_HEADER": { "TOGGLE_BUTTON": { diff --git a/libs/portal-integration-angular/assets/i18n/en.json b/libs/portal-integration-angular/assets/i18n/en.json index 06a4db4a3..fad0c3b02 100644 --- a/libs/portal-integration-angular/assets/i18n/en.json +++ b/libs/portal-integration-angular/assets/i18n/en.json @@ -135,7 +135,12 @@ }, "TABLE": { "COLUMN_NAME": "Column name", - "VALUE": "Filter value" + "VALUE": "Filter value", + "ACTIONS": "Actions", + "REMOVE_FILTER_TITLE": "Remove filter", + "REMOVE_FILTER_ARIA_LABEL": "Remove filter" + }, + "PANEL_TITLE": "Filters" } }, "OCX_SEARCH_HEADER": {