diff --git a/apps/datahub/src/app/home/search/search-filters/search-filters.component.html b/apps/datahub/src/app/home/search/search-filters/search-filters.component.html index 4a52f47ae2..bad4e63e70 100644 --- a/apps/datahub/src/app/home/search/search-filters/search-filters.component.html +++ b/apps/datahub/src/app/home/search/search-filters/search-filters.component.html @@ -78,7 +78,7 @@

*ngIf="!isOpen" (buttonClick)="open()" type="outline" - extraClass="!p-[8px]" + extraClass="!px-[8px]" data-cy="filters-expand" > more_horiz diff --git a/apps/metadata-editor-e2e/src/e2e/dashboard.cy.ts b/apps/metadata-editor-e2e/src/e2e/dashboard.cy.ts index d4004611b1..485afd352b 100644 --- a/apps/metadata-editor-e2e/src/e2e/dashboard.cy.ts +++ b/apps/metadata-editor-e2e/src/e2e/dashboard.cy.ts @@ -1,7 +1,7 @@ describe('dashboard', () => { - let originalList - let newList describe('pagination', () => { + let originalList + let newList it('should display different results on click on arrow', () => { cy.visit('/') cy.get('gn-ui-record-table') @@ -36,4 +36,33 @@ describe('dashboard', () => { }) }) }) + + // NEEDS TO WAIT UNTIL STYLE IS DONE + describe('sorting', () => { + let originalList + let newList + it('should order the result list on click', () => { + 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('.record-table-header').first().click() + cy.get('gn-ui-sort').find('gn-ui-button').first().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) + }) + }) + }) + }) }) diff --git a/apps/metadata-editor/src/app/records/records-list.component.html b/apps/metadata-editor/src/app/records/records-list.component.html index 71204476fc..01198dccb5 100644 --- a/apps/metadata-editor/src/app/records/records-list.component.html +++ b/apps/metadata-editor/src/app/records/records-list.component.html @@ -10,6 +10,8 @@

{{ title }}

[records]="results" [totalHits]="searchFacade.resultsHits$ | async" (recordSelect)="editRecord($event)" + (sortByChange)="setSortBy($event)" + [sortBy]="searchFacade.sortBy$ | async" >
{ let fixture: ComponentFixture let router: Router let searchService: SearchService + let searchFacade: SearchFacade beforeEach(() => { TestBed.configureTestingModule({ @@ -89,6 +90,7 @@ describe('RecordsListComponent', () => { }) router = TestBed.inject(Router) searchService = TestBed.inject(SearchService) + searchFacade = TestBed.inject(SearchFacade) fixture = TestBed.createComponent(RecordsListComponent) component = fixture.componentInstance fixture.detectChanges() @@ -116,6 +118,12 @@ describe('RecordsListComponent', () => { expect(pagination.currentPage).toEqual(currentPage) expect(pagination.totalPages).toEqual(totalPages) }) + it('orders the completion column', () => { + expect(searchFacade.setSortBy).toHaveBeenCalledWith([ + 'desc', + 'changeDate', + ]) + }) describe('when click on a record', () => { beforeEach(() => { table.recordSelect.emit({ uniqueIdentifier: 123 }) diff --git a/apps/metadata-editor/src/app/records/records-list.component.ts b/apps/metadata-editor/src/app/records/records-list.component.ts index e11c1f9dbb..f3511d531c 100644 --- a/apps/metadata-editor/src/app/records/records-list.component.ts +++ b/apps/metadata-editor/src/app/records/records-list.component.ts @@ -6,6 +6,7 @@ import { CatalogRecord } from '@geonetwork-ui/common/domain/record' import { SearchFacade, SearchService } from '@geonetwork-ui/feature/search' import { UiSearchModule } from '@geonetwork-ui/ui/search' import { UiElementsModule } from '@geonetwork-ui/ui/elements' +import { SortByField } from '@geonetwork-ui/common/domain/search' const includes = [ 'uuid', @@ -49,4 +50,8 @@ export class RecordsListComponent { editRecord(record: CatalogRecord) { this.router.navigate(['/edit', record.uniqueIdentifier]) } + + setSortBy(newSortBy: SortByField) { + this.searchFacade.setSortBy(newSortBy) + } } diff --git a/apps/metadata-editor/src/styles.css b/apps/metadata-editor/src/styles.css index e3a2850c1c..41d06136c0 100644 --- a/apps/metadata-editor/src/styles.css +++ b/apps/metadata-editor/src/styles.css @@ -21,3 +21,7 @@ body { .menu-title { @apply text-xl px-9 py-3; } + +.mat-mdc-button-base { + line-height: normal; +} 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 index 8a382eb52c..94d61ccaed 100644 --- 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 @@ -1,15 +1,10 @@ import { TranslateModule } from '@ngx-translate/core' -import { - componentWrapperDecorator, - Meta, - moduleMetadata, - StoryObj, -} from '@storybook/angular' +import { 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 { ButtonComponent } from '@geonetwork-ui/ui/inputs' import { PaginationButtonsComponent } from './pagination-buttons.component' import { FormsModule } from '@angular/forms' import { action } from '@storybook/addon-actions' diff --git a/libs/ui/inputs/src/lib/button/button.component.css b/libs/ui/inputs/src/lib/button/button.component.css index e69de29bb2..d458e57363 100644 --- a/libs/ui/inputs/src/lib/button/button.component.css +++ b/libs/ui/inputs/src/lib/button/button.component.css @@ -0,0 +1,5 @@ +/* makes sure icons will not make the buttons grow vertically */ +mat-icon.mat-icon { + margin-top: -0.3em; + margin-bottom: -0.3em; +} diff --git a/libs/ui/inputs/src/lib/button/button.component.stories.ts b/libs/ui/inputs/src/lib/button/button.component.stories.ts index a106ef86ea..af7bee315b 100644 --- a/libs/ui/inputs/src/lib/button/button.component.stories.ts +++ b/libs/ui/inputs/src/lib/button/button.component.stories.ts @@ -5,6 +5,7 @@ import { TRANSLATE_DEFAULT_CONFIG, UtilI18nModule, } from '@geonetwork-ui/util/i18n' +import { MatIconModule } from '@angular/material/icon' export default { title: 'Inputs/ButtonComponent', @@ -14,6 +15,7 @@ export default { imports: [ UtilI18nModule, TranslateModule.forRoot(TRANSLATE_DEFAULT_CONFIG), + MatIconModule, ], }), ], @@ -36,7 +38,22 @@ export const Primary: StoryObj = { }, render: (args) => ({ props: args, - template: - '{{ content }}', + template: `
+ + {{ content }} + + + with an icon downloading + + + globe_asia bigger + + + pest_control smaller + + + different waves shape + +
`, }), } diff --git a/libs/ui/inputs/src/lib/button/button.component.ts b/libs/ui/inputs/src/lib/button/button.component.ts index 65935b7a59..46764cb0d4 100644 --- a/libs/ui/inputs/src/lib/button/button.component.ts +++ b/libs/ui/inputs/src/lib/button/button.component.ts @@ -61,7 +61,7 @@ export class ButtonComponent { case 'primary': return 'focus:ring-4 focus:ring-primary-lightest' case 'outline': - return 'border border-gray-300 hover:border-primary-lighter focus:border-primary-lighter focus:ring-4 focus:ring-primary-lightest active:border-primary-darker' + return 'border border-gray-300 -m-[1px] hover:border-primary-lighter focus:border-primary-lighter focus:ring-4 focus:ring-primary-lightest active:border-primary-darker' case 'light': return 'focus:ring-4 focus:ring-gray-300' } 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 53365b9bb3..249ef181d4 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 @@ -10,24 +10,97 @@ > results.records.hits.displayedOn
+
-
- record.metadata.title +
+ + record.metadata.title + + expand_more + + expand_less +
-
+
record.metadata.formats
-
- record.metadata.author +
+ + record.metadata.author + + expand_more + + expand_less +
-
- record.metadata.completion +
+ + record.metadata.updatedOn + + expand_more + + expand_less +
-
- record.metadata.createdOn +
+ + record.metadata.createdOn + + expand_more + + expand_less +
{ let component: RecordTableComponent @@ -39,4 +40,41 @@ describe('RecordTableComponent', () => { ).toEqual('#1e5180') // geojson }) }) + + describe('sorting', () => { + describe('#setSortBy', () => { + let newSortBy: SortByField + beforeEach(() => { + newSortBy = null + component.sortByChange.subscribe((v) => (newSortBy = v)) + }) + it('initially sorts by ascending order', () => { + component.setSortBy('title') + expect(newSortBy).toEqual(['asc', 'title']) + }) + it('changes the order if already sorted', () => { + component.sortBy = ['asc', 'title'] + component.setSortBy('title') + expect(newSortBy).toEqual(['desc', 'title']) + }) + }) + describe('#isSortedBy', () => { + it('returns false if not sorted by this column', () => { + component.sortBy = ['desc', 'owner'] + expect(component.isSortedBy('title', 'desc')).toBe(false) + }) + it('returns true if the current sortBy is for this column', () => { + component.sortBy = ['desc', 'title'] + expect(component.isSortedBy('title', 'desc')).toBe(true) + }) + it('returns true if the current sortBy is for this column (multiple sorts)', () => { + component.sortBy = [ + ['asc', 'score'], + ['desc', 'title'], + ] + expect(component.isSortedBy('title', 'desc')).toBe(true) + expect(component.isSortedBy('title', 'asc')).toBe(false) + }) + }) + }) }) diff --git a/libs/ui/search/src/lib/record-table/record-table.component.stories.ts b/libs/ui/search/src/lib/record-table/record-table.component.stories.ts new file mode 100644 index 0000000000..1126946651 --- /dev/null +++ b/libs/ui/search/src/lib/record-table/record-table.component.stories.ts @@ -0,0 +1,34 @@ +import type { Meta, StoryObj } from '@storybook/angular' +import { moduleMetadata } from '@storybook/angular' +import { RecordTableComponent } from './record-table.component' +import { DATASET_RECORDS } from '@geonetwork-ui/common/fixtures' +import { action } from '@storybook/addon-actions' +import { MatIconModule } from '@angular/material/icon' +import { UiInputsModule } from '@geonetwork-ui/ui/inputs' + +const meta: Meta = { + component: RecordTableComponent, + title: 'Search/RecordTableComponent', + decorators: [ + moduleMetadata({ + declarations: [RecordTableComponent], + imports: [UiInputsModule, MatIconModule], + }), + ], + render: (args: RecordTableComponent) => ({ + props: { + ...args, + recordSelect: action('recordSelect'), + sortByChange: action('sortByChange'), + }, + }), +} +export default meta +type Story = StoryObj + +export const Primary: Story = { + args: { + records: DATASET_RECORDS.concat(DATASET_RECORDS, DATASET_RECORDS), + totalHits: 1234, + }, +} 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 cf71e88ff5..e98926e42a 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 @@ -6,6 +6,7 @@ import { getFileFormat, getFormatPriority, } from '@geonetwork-ui/util/shared' +import { SortByField } from '@geonetwork-ui/common/domain/search' @Component({ selector: 'gn-ui-record-table', @@ -15,7 +16,9 @@ import { export class RecordTableComponent { @Input() records: CatalogRecord[] = [] @Input() totalHits?: number + @Input() sortBy?: SortByField @Output() recordSelect = new EventEmitter() + @Output() sortByChange = new EventEmitter() dateToString(date: Date): string { return date?.toLocaleDateString(undefined, { @@ -54,4 +57,37 @@ export class RecordTableComponent { getBadgeColor(format: FileFormat): string { return getBadgeColor(format) } + + private getOrderForColumn(col: string): 'asc' | 'desc' | null { + if (!this.sortBy) { + return null + } + let order: 'asc' | 'desc' | null = null + const sortedArray = Array.isArray(this.sortBy[0]) + ? this.sortBy + : [this.sortBy] + sortedArray.forEach((sortedCol) => { + if (sortedCol[1] === col) { + order = sortedCol[0] + } + }) + return order + } + + setSortBy(col: string): void { + const sortOrder = this.getOrderForColumn(col) + let newOrder + if (sortOrder) { + newOrder = sortOrder === 'asc' ? 'desc' : 'asc' + } else { + newOrder = 'asc' + } + this.sortByChange.emit([newOrder, col]) + this.sortBy = [newOrder, col] + } + + isSortedBy(col: string, order: 'asc' | 'desc'): boolean { + const sortOrder = this.getOrderForColumn(col) + return sortOrder === order + } } diff --git a/libs/ui/search/src/lib/ui-search.module.ts b/libs/ui/search/src/lib/ui-search.module.ts index 39bff79431..4f0fecd261 100644 --- a/libs/ui/search/src/lib/ui-search.module.ts +++ b/libs/ui/search/src/lib/ui-search.module.ts @@ -26,6 +26,7 @@ import { UiElementsModule } from '@geonetwork-ui/ui/elements' import { RecordPreviewFeedComponent } from './record-preview-feed/record-preview-feed.component' import { RecordTableComponent } from './record-table/record-table.component' import { CommonModule } from '@angular/common' +import { UiInputsModule } from '@geonetwork-ui/ui/inputs' @NgModule({ declarations: [ @@ -52,6 +53,7 @@ import { CommonModule } from '@angular/common' TagInputModule, UtilSharedModule, UiWidgetsModule, + UiInputsModule, UiElementsModule, MatIconModule, RouterLink, diff --git a/translations/en.json b/translations/en.json index 5dd715fbd3..0413e33c75 100644 --- a/translations/en.json +++ b/translations/en.json @@ -175,7 +175,7 @@ "record.metadata.catalog": "Catalog", "record.metadata.completion": "Completion", "record.metadata.contact": "Contact", - "record.metadata.createdOn": "created on", + "record.metadata.createdOn": "Created on", "record.metadata.details": "Details", "record.metadata.download": "Downloads", "record.metadata.formats": "Formats", @@ -188,7 +188,7 @@ "record.metadata.publications": "publications", "record.metadata.related": "Related records", "record.metadata.sheet": "Original metadata sheet", - "record.metadata.title": "title", + "record.metadata.title": "Title", "record.metadata.updateFrequency": "Update Frequency", "record.metadata.updateStatus": "Update Status", "record.metadata.updatedOn": "Updated On", diff --git a/translations/fr.json b/translations/fr.json index d74730a09a..ed273ea155 100644 --- a/translations/fr.json +++ b/translations/fr.json @@ -171,14 +171,14 @@ "record.externalViewer.open": "Ouvrir dans le visualiseur externe", "record.metadata.about": "À propos", "record.metadata.api": "API", - "record.metadata.author": "", + "record.metadata.author": "Auteur", "record.metadata.catalog": "Catalogue", - "record.metadata.completion": "", + "record.metadata.completion": "Mis à jour", "record.metadata.contact": "Contact", - "record.metadata.createdOn": "", + "record.metadata.createdOn": "Créé le", "record.metadata.details": "Détails", "record.metadata.download": "Téléchargements", - "record.metadata.formats": "", + "record.metadata.formats": "Formats", "record.metadata.isOpenData": "Donnée Ouverte", "record.metadata.keywords": "Mots clés", "record.metadata.links": "Liens", @@ -188,7 +188,7 @@ "record.metadata.publications": "données", "record.metadata.related": "Voir aussi", "record.metadata.sheet": "Fiche de métadonnées d'origine", - "record.metadata.title": "titre", + "record.metadata.title": "Titre", "record.metadata.updateFrequency": "Fréquence de mise à jour", "record.metadata.updateStatus": "Statut de mise à jour", "record.metadata.updatedOn": "Dernière mise à jour",