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 {}