diff --git a/apps/metadata-editor-e2e/src/e2e/dashboard.cy.ts b/apps/metadata-editor-e2e/src/e2e/dashboard.cy.ts new file mode 100644 index 0000000000..d4004611b1 --- /dev/null +++ b/apps/metadata-editor-e2e/src/e2e/dashboard.cy.ts @@ -0,0 +1,39 @@ +describe('dashboard', () => { + let originalList + let newList + describe('pagination', () => { + it('should display different results on click on arrow', () => { + cy.visit('/') + cy.get('gn-ui-record-table') + .find('.record-table-col') + .first() + .as('pageOne') + + cy.get('@pageOne') + .invoke('text') + .then((list) => { + originalList = list.trim() + cy.get('gn-ui-pagination-buttons').find('gn-ui-button').last().click() + cy.get('gn-ui-record-table') + .find('.record-table-col') + .first() + .invoke('text') + .then((list) => { + newList = list.trim() + expect(newList).not.to.be(originalList) + }) + }) + }) + it('should display different results on click on specific page', () => { + cy.get('gn-ui-pagination-buttons').find('gn-ui-button').eq(1).click() + cy.get('gn-ui-record-table') + .find('.record-table-col') + .first() + .invoke('text') + .then((list) => { + newList = list.trim() + expect(newList).to.be(originalList) + }) + }) + }) +}) diff --git a/apps/metadata-editor/src/app/dashboard/dashboard-page.component.html b/apps/metadata-editor/src/app/dashboard/dashboard-page.component.html index 97292fae30..9774aeceaa 100644 --- a/apps/metadata-editor/src/app/dashboard/dashboard-page.component.html +++ b/apps/metadata-editor/src/app/dashboard/dashboard-page.component.html @@ -43,12 +43,11 @@

results.records.hits.displayedOn
- + >
diff --git a/apps/metadata-editor/src/app/dashboard/dashboard-page.component.spec.ts b/apps/metadata-editor/src/app/dashboard/dashboard-page.component.spec.ts index b75e7e4160..c1ac85ebe6 100644 --- a/apps/metadata-editor/src/app/dashboard/dashboard-page.component.spec.ts +++ b/apps/metadata-editor/src/app/dashboard/dashboard-page.component.spec.ts @@ -30,12 +30,12 @@ export class RecordTableComponent { } @Component({ // eslint-disable-next-line - selector: 'gn-ui-pagination', + selector: 'gn-ui-pagination-buttons', template: '', }) -export class PaginationComponent { +export class PaginationButtonsComponent { @Input() currentPage = 1 - @Input() nPages = 1 + @Input() totalPages = 1 @Input() hideButton = false @Output() newCurrentPageEvent = new EventEmitter() } @@ -64,7 +64,7 @@ describe('DashboardPageComponent', () => { schemas: [NO_ERRORS_SCHEMA], declarations: [ DashboardPageComponent, - PaginationComponent, + PaginationButtonsComponent, RecordTableComponent, ], providers: [ @@ -117,7 +117,7 @@ describe('DashboardPageComponent', () => { By.directive(RecordTableComponent) ).componentInstance pagination = fixture.debugElement.query( - By.directive(PaginationComponent) + By.directive(PaginationButtonsComponent) ).componentInstance }) it('displays record table', () => { @@ -126,7 +126,7 @@ describe('DashboardPageComponent', () => { it('displays pagination', () => { expect(pagination).toBeTruthy() expect(pagination.currentPage).toEqual(currentPage) - expect(pagination.nPages).toEqual(totalPages) + expect(pagination.totalPages).toEqual(totalPages) }) describe('when click on a record', () => { beforeEach(() => { diff --git a/libs/ui/elements/src/lib/pagination-buttons/pagination-buttons.component.css b/libs/ui/elements/src/lib/pagination-buttons/pagination-buttons.component.css new file mode 100644 index 0000000000..e69de29bb2 diff --git a/libs/ui/elements/src/lib/pagination-buttons/pagination-buttons.component.html b/libs/ui/elements/src/lib/pagination-buttons/pagination-buttons.component.html new file mode 100644 index 0000000000..c2c097dfd9 --- /dev/null +++ b/libs/ui/elements/src/lib/pagination-buttons/pagination-buttons.component.html @@ -0,0 +1,31 @@ +
+
+ + chevron_left + + + + {{ page }} + + + {{ page }} + + + + chevron_right + +
+
diff --git a/libs/ui/elements/src/lib/pagination-buttons/pagination-buttons.component.spec.ts b/libs/ui/elements/src/lib/pagination-buttons/pagination-buttons.component.spec.ts new file mode 100644 index 0000000000..c2e7e4559f --- /dev/null +++ b/libs/ui/elements/src/lib/pagination-buttons/pagination-buttons.component.spec.ts @@ -0,0 +1,112 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing' +import { PaginationButtonsComponent } from './pagination-buttons.component' + +describe('PaginationButtonsComponent', () => { + let component: PaginationButtonsComponent + let fixture: ComponentFixture + + const mockChangePage = (page) => { + component.setPage(page) + } + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [PaginationButtonsComponent], + }).compileComponents() + + fixture = TestBed.createComponent(PaginationButtonsComponent) + component = fixture.componentInstance + component.currentPage = 3 + component.totalPages = 10 + component.calculateVisiblePages() + component.changePage = mockChangePage + fixture.detectChanges() + }) + + it('should create', () => { + expect(component).toBeTruthy() + }) + describe('when using next arrow', () => { + beforeEach(() => { + component.currentPage = 2 + const paginationButtons = + fixture.nativeElement.querySelectorAll('gn-ui-button') + paginationButtons.forEach((buttonElement) => { + const matIcon = buttonElement.querySelector('mat-icon') + if (matIcon && matIcon.textContent.trim() === 'chevron_right') { + buttonElement.dispatchEvent(new Event('buttonClick')) + } + }) + }) + it('should access next page on click', () => { + expect(component.currentPage).toBe(3) + }) + }) + describe('when using previous arrow', () => { + beforeEach(() => { + component.currentPage = 4 + const paginationButtons = + fixture.nativeElement.querySelectorAll('gn-ui-button') + paginationButtons.forEach((buttonElement) => { + const matIcon = buttonElement.querySelector('mat-icon') + if (matIcon && matIcon.textContent.trim() === 'chevron_left') { + buttonElement.dispatchEvent(new Event('buttonClick')) + } + }) + }) + it('is should access previous page', () => { + expect(component.currentPage).toBe(3) + }) + }) + describe('when accessing first page', () => { + beforeEach(() => { + component.currentPage = 1 + fixture.detectChanges() + }) + it('is should disable the previous arrow', () => { + const paginationButtons = + fixture.nativeElement.querySelectorAll('gn-ui-button') + paginationButtons.forEach((buttonElement) => { + const matIcon = buttonElement.querySelector('mat-icon') + if (matIcon && matIcon.textContent.trim() === 'chevron_left') { + const prevBtn = buttonElement.disabled + expect(prevBtn).toBeTruthy() + } + }) + }) + }) + describe('when accessing last page', () => { + beforeEach(() => { + component.currentPage = 10 + fixture.detectChanges() + }) + it('is should disable the next arrow', () => { + const paginationButtons = + fixture.nativeElement.querySelectorAll('gn-ui-button') + paginationButtons.forEach((buttonElement) => { + const matIcon = buttonElement.querySelector('mat-icon') + if (matIcon && matIcon.textContent.trim() === 'chevron_right') { + const nextBtn = buttonElement.disabled + expect(nextBtn).toBeTruthy() + } + }) + }) + }) + describe('when clicking on page button', () => { + beforeEach(() => { + const paginationButtons = + fixture.nativeElement.querySelectorAll('gn-ui-button') + const pageBtnList = [] + paginationButtons.forEach((buttonElement) => { + const matIcon = buttonElement.querySelector('mat-icon') + if (!matIcon) { + pageBtnList.push(buttonElement) + } + }) + pageBtnList[1].dispatchEvent(new Event('buttonClick')) + }) + it('is should access the requested page', () => { + expect(component.currentPage).toBe(2) + }) + }) +}) diff --git a/libs/ui/elements/src/lib/pagination-buttons/pagination-buttons.component.stories.ts b/libs/ui/elements/src/lib/pagination-buttons/pagination-buttons.component.stories.ts new file mode 100644 index 0000000000..8a382eb52c --- /dev/null +++ b/libs/ui/elements/src/lib/pagination-buttons/pagination-buttons.component.stories.ts @@ -0,0 +1,47 @@ +import { TranslateModule } from '@ngx-translate/core' +import { + componentWrapperDecorator, + Meta, + moduleMetadata, + StoryObj, +} from '@storybook/angular' +import { + TRANSLATE_DEFAULT_CONFIG, + UtilI18nModule, +} from '@geonetwork-ui/util/i18n' +import { UiInputsModule, ButtonComponent } from '@geonetwork-ui/ui/inputs' +import { PaginationButtonsComponent } from './pagination-buttons.component' +import { FormsModule } from '@angular/forms' +import { action } from '@storybook/addon-actions' +import { MatIcon } from '@angular/material/icon' + +export default { + title: 'Elements/PaginationButtonsComponent', + component: PaginationButtonsComponent, + decorators: [ + moduleMetadata({ + declarations: [ButtonComponent, MatIcon], + imports: [ + UtilI18nModule, + FormsModule, + TranslateModule.forRoot(TRANSLATE_DEFAULT_CONFIG), + ], + }), + ], + render: (args: PaginationButtonsComponent) => ({ + props: { + ...args, + newCurrentPageEvent: action('newCurrentPageEvent'), + }, + }), +} as Meta + +export const Primary: StoryObj = { + args: { + currentPage: 1, + totalPages: 10, + }, + parameters: { + layout: 'centered', + }, +} diff --git a/libs/ui/elements/src/lib/pagination-buttons/pagination-buttons.component.ts b/libs/ui/elements/src/lib/pagination-buttons/pagination-buttons.component.ts new file mode 100644 index 0000000000..38f5f8b768 --- /dev/null +++ b/libs/ui/elements/src/lib/pagination-buttons/pagination-buttons.component.ts @@ -0,0 +1,68 @@ +import { + Component, + EventEmitter, + Input, + OnChanges, + Output, +} from '@angular/core' + +@Component({ + selector: 'gn-ui-pagination-buttons', + templateUrl: './pagination-buttons.component.html', + styleUrls: ['./pagination-buttons.component.css'], +}) +export class PaginationButtonsComponent implements OnChanges { + @Input() currentPage: number + @Input() totalPages: number + visiblePages: (number | '...')[] = [] + @Output() newCurrentPageEvent = new EventEmitter() + + ngOnChanges(): void { + this.calculateVisiblePages() + } + + calculateVisiblePages(): void { + const maxVisiblePages = 5 + const halfVisible = Math.floor(maxVisiblePages / 2) + const startPage = Math.max(this.currentPage - halfVisible, 1) + const endPage = Math.min(this.currentPage + halfVisible, this.totalPages) + + const visiblePages: (number | '...')[] = [] + if (startPage > 1) { + visiblePages.push(1) + if (startPage > 2) { + visiblePages.push('...') + } + } + for (let page = startPage; page <= endPage; page++) { + visiblePages.push(page) + } + if (endPage < this.totalPages) { + if (endPage < this.totalPages - 1) { + visiblePages.push('...') + } + visiblePages.push(this.totalPages) + } + + this.visiblePages = visiblePages + } + + changePage(page) { + this.setPage(page) + } + + nextPage() { + this.setPage(this.currentPage + 1) + } + + previousPage() { + this.setPage(this.currentPage - 1) + } + + setPage(newPage) { + if (!Number.isInteger(newPage)) return + this.currentPage = newPage + this.calculateVisiblePages() + this.newCurrentPageEvent.emit(this.currentPage) + } +} diff --git a/libs/ui/elements/src/lib/pagination/pagination.component.stories.ts b/libs/ui/elements/src/lib/pagination/pagination.component.stories.ts index 22d4422856..29d9ca1172 100644 --- a/libs/ui/elements/src/lib/pagination/pagination.component.stories.ts +++ b/libs/ui/elements/src/lib/pagination/pagination.component.stories.ts @@ -15,7 +15,7 @@ import { MatIcon } from '@angular/material/icon' import { FormsModule } from '@angular/forms' export default { - title: 'Layout/PaginationComponent', + title: 'Elements/PaginationComponent', component: PaginationComponent, decorators: [ moduleMetadata({ diff --git a/libs/ui/elements/src/lib/ui-elements.module.ts b/libs/ui/elements/src/lib/ui-elements.module.ts index 74a897c6bb..6e0566fa54 100644 --- a/libs/ui/elements/src/lib/ui-elements.module.ts +++ b/libs/ui/elements/src/lib/ui-elements.module.ts @@ -24,6 +24,7 @@ import { FormsModule } from '@angular/forms' import { AvatarComponent } from './avatar/avatar.component' import { UserPreviewComponent } from './user-preview/user-preview.component' import { GnUiLinkifyDirective } from './metadata-info/linkify.directive' +import { PaginationButtonsComponent } from './pagination-buttons/pagination-buttons.component' @NgModule({ imports: [ @@ -55,6 +56,7 @@ import { GnUiLinkifyDirective } from './metadata-info/linkify.directive' AvatarComponent, UserPreviewComponent, GnUiLinkifyDirective, + PaginationButtonsComponent, ], exports: [ MetadataInfoComponent, @@ -71,6 +73,7 @@ import { GnUiLinkifyDirective } from './metadata-info/linkify.directive' ThumbnailComponent, AvatarComponent, UserPreviewComponent, + PaginationButtonsComponent, ], }) export class UiElementsModule {}