From 1e9aa1bc190a51be59f2a8d9103edd83322a04cb Mon Sep 17 00:00:00 2001 From: Angelika Kinas Date: Mon, 30 Oct 2023 11:37:54 +0100 Subject: [PATCH 1/4] feat(ME): Add checkboxes to records --- .../record-table/record-table.component.html | 33 ++++++++++++--- .../record-table.component.spec.ts | 32 ++++++++++++++ .../record-table/record-table.component.ts | 42 +++++++++++++++++++ libs/ui/search/src/lib/ui-search.module.ts | 2 + 4 files changed, 103 insertions(+), 6 deletions(-) diff --git a/libs/ui/search/src/lib/record-table/record-table.component.html b/libs/ui/search/src/lib/record-table/record-table.component.html index 6343d7a8a1..a02ed0c5e5 100644 --- a/libs/ui/search/src/lib/record-table/record-table.component.html +++ b/libs/ui/search/src/lib/record-table/record-table.component.html @@ -44,14 +44,24 @@
-
+
+
+ + +
record.metadata.title @@ -75,7 +85,7 @@
record.metadata.author @@ -96,7 +106,7 @@
record.metadata.updatedOn @@ -117,7 +127,7 @@
record.metadata.createdOn @@ -141,6 +151,17 @@ (click)="recordSelect.emit(record)" *ngFor="let record of records" > +
+ +
{{ record.title }}
diff --git a/libs/ui/search/src/lib/record-table/record-table.component.spec.ts b/libs/ui/search/src/lib/record-table/record-table.component.spec.ts index 142754da15..7156ce1043 100644 --- a/libs/ui/search/src/lib/record-table/record-table.component.spec.ts +++ b/libs/ui/search/src/lib/record-table/record-table.component.spec.ts @@ -3,6 +3,7 @@ import { DATASET_RECORDS } from '@geonetwork-ui/common/fixtures' import { RecordTableComponent } from './record-table.component' import { SortByField } from '@geonetwork-ui/common/domain/search' +import { CatalogRecord } from '@geonetwork-ui/common/domain/record' describe('RecordTableComponent', () => { let component: RecordTableComponent @@ -76,5 +77,36 @@ describe('RecordTableComponent', () => { expect(component.isSortedBy('title', 'asc')).toBe(false) }) }) + + describe('isChecked', () => { + it('should return true when the record is in the selectedRecords array', () => { + const component = new RecordTableComponent() + component.selectedRecords = ['1', '2', '3'] + const record = { uniqueIdentifier: '2' } as any as CatalogRecord + expect(component.isChecked(record)).toBe(true) + }) + + it('should return false when the record is not in the selectedRecords array', () => { + const component = new RecordTableComponent() + component.selectedRecords = ['1', '2', '3'] + const record = { uniqueIdentifier: '4' } as any as CatalogRecord + expect(component.isChecked(record)).toBe(false) + }) + }) + + describe('#handleCheckboxClick', () => { + it('should add unique identifier to selectedRecords when checkbox is clicked for a record that is not already selected', () => { + const component = new RecordTableComponent() + const record = { uniqueIdentifier: '1' } + component.selectedRecords = [] + + component.handleCheckboxClick( + {} as any as Event, + record as any as CatalogRecord + ) + + expect(component.selectedRecords).toEqual(['1']) + }) + }) }) }) diff --git a/libs/ui/search/src/lib/record-table/record-table.component.ts b/libs/ui/search/src/lib/record-table/record-table.component.ts index 2be118f1fb..af537fa5c1 100644 --- a/libs/ui/search/src/lib/record-table/record-table.component.ts +++ b/libs/ui/search/src/lib/record-table/record-table.component.ts @@ -14,6 +14,8 @@ import { SortByField } from '@geonetwork-ui/common/domain/search' styleUrls: ['./record-table.component.css'], }) export class RecordTableComponent { + selectedRecords = [] + @Input() records: any[] = [] @Input() totalHits?: number @Input() sortBy?: SortByField @@ -90,4 +92,44 @@ export class RecordTableComponent { const sortOrder = this.getOrderForColumn(col) return sortOrder === order } + + isChecked(record: CatalogRecord) { + if (this.selectedRecords.includes(record.uniqueIdentifier)) { + return true + } + return false + } + + handleCheckboxClick(event: MouseEvent | Event, record: CatalogRecord) { + event.stopPropagation() + if (this.isChecked(record)) { + this.selectedRecords = this.selectedRecords.filter( + (val) => val !== record.uniqueIdentifier + ) + } else { + this.selectedRecords.push(record.uniqueIdentifier) + } + } + + selectAll() { + if (this.isAllSelected()) { + this.selectedRecords = [] + } else { + this.selectedRecords = this.records.map( + (record) => record.uniqueIdentifier + ) + } + } + + isAllSelected() { + if (this.selectedRecords.length === this.records.length) { + const allRecords = this.records.filter((record) => + this.selectedRecords.includes(record.uniqueIdentifier) + ) + if (allRecords.length === this.records.length) { + return true + } + } + return false + } } diff --git a/libs/ui/search/src/lib/ui-search.module.ts b/libs/ui/search/src/lib/ui-search.module.ts index 4f0fecd261..3f231d3450 100644 --- a/libs/ui/search/src/lib/ui-search.module.ts +++ b/libs/ui/search/src/lib/ui-search.module.ts @@ -27,6 +27,7 @@ import { RecordPreviewFeedComponent } from './record-preview-feed/record-preview import { RecordTableComponent } from './record-table/record-table.component' import { CommonModule } from '@angular/common' import { UiInputsModule } from '@geonetwork-ui/ui/inputs' +import { MatCheckboxModule } from '@angular/material/checkbox' @NgModule({ declarations: [ @@ -56,6 +57,7 @@ import { UiInputsModule } from '@geonetwork-ui/ui/inputs' UiInputsModule, UiElementsModule, MatIconModule, + MatCheckboxModule, RouterLink, ], exports: [ From 5a82d0064213cb5a5c3c15138bd315a777864ef9 Mon Sep 17 00:00:00 2001 From: Angelika Kinas Date: Tue, 24 Oct 2023 15:38:38 +0200 Subject: [PATCH 2/4] feat(input): Create new checkbox component --- .../src/lib/checkbox/checkbox.component.css | 0 .../src/lib/checkbox/checkbox.component.html | 8 ++++ .../lib/checkbox/checkbox.component.spec.ts | 45 ++++++++++++++++++ .../checkbox/checkbox.component.stories.ts | 46 +++++++++++++++++++ .../src/lib/checkbox/checkbox.component.ts | 24 ++++++++++ libs/ui/inputs/src/lib/ui-inputs.module.ts | 2 + 6 files changed, 125 insertions(+) create mode 100644 libs/ui/inputs/src/lib/checkbox/checkbox.component.css create mode 100644 libs/ui/inputs/src/lib/checkbox/checkbox.component.html create mode 100644 libs/ui/inputs/src/lib/checkbox/checkbox.component.spec.ts create mode 100644 libs/ui/inputs/src/lib/checkbox/checkbox.component.stories.ts create mode 100644 libs/ui/inputs/src/lib/checkbox/checkbox.component.ts diff --git a/libs/ui/inputs/src/lib/checkbox/checkbox.component.css b/libs/ui/inputs/src/lib/checkbox/checkbox.component.css new file mode 100644 index 0000000000..e69de29bb2 diff --git a/libs/ui/inputs/src/lib/checkbox/checkbox.component.html b/libs/ui/inputs/src/lib/checkbox/checkbox.component.html new file mode 100644 index 0000000000..08730cb3aa --- /dev/null +++ b/libs/ui/inputs/src/lib/checkbox/checkbox.component.html @@ -0,0 +1,8 @@ + diff --git a/libs/ui/inputs/src/lib/checkbox/checkbox.component.spec.ts b/libs/ui/inputs/src/lib/checkbox/checkbox.component.spec.ts new file mode 100644 index 0000000000..a9cffe49d6 --- /dev/null +++ b/libs/ui/inputs/src/lib/checkbox/checkbox.component.spec.ts @@ -0,0 +1,45 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing' +import { CheckboxComponent } from './checkbox.component' +import { MatCheckboxModule } from '@angular/material/checkbox' + +describe('CheckboxComponent', () => { + let component: CheckboxComponent + let fixture: ComponentFixture + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [CheckboxComponent], + imports: [MatCheckboxModule], + }).compileComponents() + + fixture = TestBed.createComponent(CheckboxComponent) + component = fixture.componentInstance + fixture.detectChanges() + }) + + it('should create', () => { + expect(component).toBeTruthy() + }) + + describe('click when unchecked', () => { + beforeEach(() => { + component.checked = false + }) + it('should invert checked state when being clicked', () => { + component.handleClick() + + expect(component.checked).toBe(true) + }) + }) + + describe('click when checked', () => { + beforeEach(() => { + component.checked = true + }) + it('should invert checked state when being clicked', () => { + component.handleClick() + + expect(component.checked).toBe(false) + }) + }) +}) diff --git a/libs/ui/inputs/src/lib/checkbox/checkbox.component.stories.ts b/libs/ui/inputs/src/lib/checkbox/checkbox.component.stories.ts new file mode 100644 index 0000000000..abd0215d90 --- /dev/null +++ b/libs/ui/inputs/src/lib/checkbox/checkbox.component.stories.ts @@ -0,0 +1,46 @@ +import { + applicationConfig, + Meta, + moduleMetadata, + StoryObj, +} from '@storybook/angular' + +import { BrowserModule } from '@angular/platform-browser' +import { BrowserAnimationsModule } from '@angular/platform-browser/animations' +import { CommonModule } from '@angular/common' +import { importProvidersFrom } from '@angular/core' +import { CheckboxComponent } from './checkbox.component' +import { MatCheckbox, MatCheckboxModule } from '@angular/material/checkbox' + +export default { + title: 'Inputs/CheckboxComponent', + component: CheckboxComponent, + decorators: [ + moduleMetadata({ + declarations: [MatCheckbox], + imports: [], + }), + applicationConfig({ + providers: [ + importProvidersFrom( + BrowserModule, + BrowserAnimationsModule, + CommonModule, + MatCheckboxModule + ), + ], + }), + ], +} as Meta + +export const Primary: StoryObj = { + args: { + checked: false, + indeterminate: false, + }, + argTypes: { + changed: { + action: 'changed', + }, + }, +} diff --git a/libs/ui/inputs/src/lib/checkbox/checkbox.component.ts b/libs/ui/inputs/src/lib/checkbox/checkbox.component.ts new file mode 100644 index 0000000000..a20c4ca5c6 --- /dev/null +++ b/libs/ui/inputs/src/lib/checkbox/checkbox.component.ts @@ -0,0 +1,24 @@ +import { + ChangeDetectionStrategy, + Component, + EventEmitter, + Input, + Output, +} from '@angular/core' + +@Component({ + selector: 'gn-ui-checkbox', + templateUrl: './checkbox.component.html', + styleUrls: ['./checkbox.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class CheckboxComponent { + @Input() checked = false + @Input() indeterminate = false + @Output() changed = new EventEmitter() + + handleClick() { + this.checked = !this.checked + this.changed.emit(this.checked) + } +} diff --git a/libs/ui/inputs/src/lib/ui-inputs.module.ts b/libs/ui/inputs/src/lib/ui-inputs.module.ts index a5c5e02445..a77a135bc8 100644 --- a/libs/ui/inputs/src/lib/ui-inputs.module.ts +++ b/libs/ui/inputs/src/lib/ui-inputs.module.ts @@ -34,6 +34,7 @@ import { CheckToggleComponent } from './check-toggle/check-toggle.component' import { CopyTextButtonComponent } from './copy-text-button/copy-text-button.component' import { MatTooltipModule } from '@angular/material/tooltip' import { CommonModule } from '@angular/common' +import { CheckboxComponent } from './checkbox/checkbox.component' @NgModule({ declarations: [ @@ -58,6 +59,7 @@ import { CommonModule } from '@angular/common' FormFieldTemporalExtentComponent, CheckToggleComponent, CopyTextButtonComponent, + CheckboxComponent, ], imports: [ CommonModule, From 1d2b21902ca402e70344487e66a56349abae2bf9 Mon Sep 17 00:00:00 2001 From: Angelika Kinas Date: Tue, 24 Oct 2023 16:22:59 +0200 Subject: [PATCH 3/4] feat(ME): Use gn-ui-checkbox in the records table --- .../src/lib/checkbox/checkbox.component.html | 2 +- .../src/lib/checkbox/checkbox.component.ts | 5 ++- libs/ui/inputs/src/lib/ui-inputs.module.ts | 1 + .../record-table/record-table.component.html | 23 ++++-------- .../record-table.component.spec.ts | 37 +++++++++++++++++-- .../record-table/record-table.component.ts | 19 +++++++--- 6 files changed, 61 insertions(+), 26 deletions(-) diff --git a/libs/ui/inputs/src/lib/checkbox/checkbox.component.html b/libs/ui/inputs/src/lib/checkbox/checkbox.component.html index 08730cb3aa..947220f73f 100644 --- a/libs/ui/inputs/src/lib/checkbox/checkbox.component.html +++ b/libs/ui/inputs/src/lib/checkbox/checkbox.component.html @@ -3,6 +3,6 @@ type="checkbox" [checked]="checked" [indeterminate]="indeterminate" - (click)="handleClick()" + (click)="handleClick($event)" color="primary" > diff --git a/libs/ui/inputs/src/lib/checkbox/checkbox.component.ts b/libs/ui/inputs/src/lib/checkbox/checkbox.component.ts index a20c4ca5c6..c9903a0495 100644 --- a/libs/ui/inputs/src/lib/checkbox/checkbox.component.ts +++ b/libs/ui/inputs/src/lib/checkbox/checkbox.component.ts @@ -14,10 +14,11 @@ import { }) export class CheckboxComponent { @Input() checked = false - @Input() indeterminate = false + @Input() indeterminate? = false @Output() changed = new EventEmitter() - handleClick() { + handleClick(event: Event) { + event.stopPropagation() this.checked = !this.checked this.changed.emit(this.checked) } diff --git a/libs/ui/inputs/src/lib/ui-inputs.module.ts b/libs/ui/inputs/src/lib/ui-inputs.module.ts index a77a135bc8..a595dbf425 100644 --- a/libs/ui/inputs/src/lib/ui-inputs.module.ts +++ b/libs/ui/inputs/src/lib/ui-inputs.module.ts @@ -91,6 +91,7 @@ import { CheckboxComponent } from './checkbox/checkbox.component' FormFieldComponent, CheckToggleComponent, CopyTextButtonComponent, + CheckboxComponent, ], }) export class UiInputsModule {} diff --git a/libs/ui/search/src/lib/record-table/record-table.component.html b/libs/ui/search/src/lib/record-table/record-table.component.html index a02ed0c5e5..62bf4d7867 100644 --- a/libs/ui/search/src/lib/record-table/record-table.component.html +++ b/libs/ui/search/src/lib/record-table/record-table.component.html @@ -50,14 +50,12 @@
- - +
-
- + + (changed)="handleRecordSelectedChange($event, record)" + >
{{ record.title }} diff --git a/libs/ui/search/src/lib/record-table/record-table.component.spec.ts b/libs/ui/search/src/lib/record-table/record-table.component.spec.ts index 7156ce1043..a043f39804 100644 --- a/libs/ui/search/src/lib/record-table/record-table.component.spec.ts +++ b/libs/ui/search/src/lib/record-table/record-table.component.spec.ts @@ -16,6 +16,11 @@ describe('RecordTableComponent', () => { fixture = TestBed.createComponent(RecordTableComponent) component = fixture.componentInstance + component.records = [ + { uniqueIdentifier: '1' }, + { uniqueIdentifier: '2' }, + { uniqueIdentifier: '3' }, + ] as CatalogRecord[] fixture.detectChanges() }) @@ -94,19 +99,45 @@ describe('RecordTableComponent', () => { }) }) - describe('#handleCheckboxClick', () => { + describe('#handleRecordSelectedChange', () => { it('should add unique identifier to selectedRecords when checkbox is clicked for a record that is not already selected', () => { const component = new RecordTableComponent() const record = { uniqueIdentifier: '1' } component.selectedRecords = [] - component.handleCheckboxClick( - {} as any as Event, + component.handleRecordSelectedChange( + true, record as any as CatalogRecord ) expect(component.selectedRecords).toEqual(['1']) }) }) + + describe('#isAllSelected', () => { + it('returns true if all records are selected', () => { + component.selectedRecords = ['1', '2', '3'] + expect(component.isAllSelected()).toBe(true) + }) + it('returns false otherwise', () => { + component.selectedRecords = ['1'] + expect(component.isAllSelected()).toBe(false) + }) + }) + }) + + describe('#isSomeSelected', () => { + it('returns false if all records are selected', () => { + component.selectedRecords = ['1', '2', '3'] + expect(component.isSomeSelected()).toBe(false) + }) + it('returns true if more than one record selected', () => { + component.selectedRecords = ['2', '3'] + expect(component.isSomeSelected()).toBe(true) + }) + it('returns false if no record selected', () => { + component.selectedRecords = [] + expect(component.isSomeSelected()).toBe(false) + }) }) }) diff --git a/libs/ui/search/src/lib/record-table/record-table.component.ts b/libs/ui/search/src/lib/record-table/record-table.component.ts index af537fa5c1..6cdf956f42 100644 --- a/libs/ui/search/src/lib/record-table/record-table.component.ts +++ b/libs/ui/search/src/lib/record-table/record-table.component.ts @@ -14,7 +14,7 @@ import { SortByField } from '@geonetwork-ui/common/domain/search' styleUrls: ['./record-table.component.css'], }) export class RecordTableComponent { - selectedRecords = [] + selectedRecords: string[] = [] @Input() records: any[] = [] @Input() totalHits?: number @@ -100,9 +100,8 @@ export class RecordTableComponent { return false } - handleCheckboxClick(event: MouseEvent | Event, record: CatalogRecord) { - event.stopPropagation() - if (this.isChecked(record)) { + handleRecordSelectedChange(selected: boolean, record: CatalogRecord) { + if (!selected) { this.selectedRecords = this.selectedRecords.filter( (val) => val !== record.uniqueIdentifier ) @@ -121,7 +120,7 @@ export class RecordTableComponent { } } - isAllSelected() { + isAllSelected(): boolean { if (this.selectedRecords.length === this.records.length) { const allRecords = this.records.filter((record) => this.selectedRecords.includes(record.uniqueIdentifier) @@ -132,4 +131,14 @@ export class RecordTableComponent { } return false } + + isSomeSelected(): boolean { + if ( + this.selectedRecords.length > 0 && + this.selectedRecords.length < this.records.length + ) { + return true + } + return false + } } From 80c693d9f1caec5db16402db6fd6befc6c6867a8 Mon Sep 17 00:00:00 2001 From: Angelika Kinas Date: Tue, 24 Oct 2023 16:33:45 +0200 Subject: [PATCH 4/4] feat(ME): Add styling for the table and fix tests --- .../lib/checkbox/checkbox.component.spec.ts | 6 +++-- .../record-table/record-table.component.html | 27 ++++++++++--------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/libs/ui/inputs/src/lib/checkbox/checkbox.component.spec.ts b/libs/ui/inputs/src/lib/checkbox/checkbox.component.spec.ts index a9cffe49d6..384c47f8d7 100644 --- a/libs/ui/inputs/src/lib/checkbox/checkbox.component.spec.ts +++ b/libs/ui/inputs/src/lib/checkbox/checkbox.component.spec.ts @@ -26,7 +26,8 @@ describe('CheckboxComponent', () => { component.checked = false }) it('should invert checked state when being clicked', () => { - component.handleClick() + const event = new Event('click') + component.handleClick(event) expect(component.checked).toBe(true) }) @@ -37,7 +38,8 @@ describe('CheckboxComponent', () => { component.checked = true }) it('should invert checked state when being clicked', () => { - component.handleClick() + const event = new Event('click') + component.handleClick(event) expect(component.checked).toBe(false) }) diff --git a/libs/ui/search/src/lib/record-table/record-table.component.html b/libs/ui/search/src/lib/record-table/record-table.component.html index 62bf4d7867..809973bf98 100644 --- a/libs/ui/search/src/lib/record-table/record-table.component.html +++ b/libs/ui/search/src/lib/record-table/record-table.component.html @@ -48,15 +48,15 @@ *ngIf="!records[0].name" >
-
-
- - -
+
+ + +
+
-
+
{{ record.title }}
person {{ formatUserInfo(record.extras?.ownerInfo) }}
-
+
{{ dateToString(record.recordUpdated) }}
-
+
{{ dateToString(record.recordCreated) }}