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": {