diff --git a/apps/datahub-e2e/src/e2e/search.cy.ts b/apps/datahub-e2e/src/e2e/search.cy.ts index 5f5a688a..efcf1fba 100644 --- a/apps/datahub-e2e/src/e2e/search.cy.ts +++ b/apps/datahub-e2e/src/e2e/search.cy.ts @@ -158,21 +158,17 @@ describe('search', () => { it('should display the search results in a grid', () => { cy.get('mel-datahub-results-list-grid').should('be.visible') }) - it('should filter the results when selecting a filter value (licence)', () => { - cy.get('@filters').eq(3).click() + it('should filter the results when selecting a filter value (producer)', () => { + cy.get('@filters').eq(1).click() getFilterOptions() cy.get('@options').eq(1).click() - cy.get('@result-cards').should('have.length', 2) + cy.get('@result-cards').should('have.length', 1) cy.get('@result-cards') .first() .find('h1') - .should('have.text', ' Accroches vélos MEL ') - cy.get('@result-cards') - .eq(1) - .find('h1') .should( 'have.text', - ' Mat éolien construit ou en projet dans les Hauts de France ' + " Concentrations annuelles de polluants dans l'air ambiant issues du réseau permanent de mesures en région Hauts-de-France " ) }) it('should filter the results when selecting multiple filter values (producer)', () => { @@ -223,6 +219,32 @@ describe('search', () => { ' Aucune correspondance. ' ) }) + describe('expanded search panel', () => { + beforeEach(() => { + cy.get('[data-cy="filterExpandBtn"]').as('expandBtn') + }) + it('should expand the search panel and show more filters on click', () => { + cy.get('mel-datahub-filter-dropdown').should('have.length', 3) + cy.get('@expandBtn').click() + cy.get('mel-datahub-filter-dropdown').should('have.length', 6) + }) + it('should show the reset button and reset the filters on click', () => { + cy.get('@expandBtn').click() + cy.get('@filters').eq(3).click() + getFilterOptions() + cy.get('@options').eq(1).click() + cy.get('@result-cards').should('have.length', 2) + cy.get('body').click() + cy.get('[data-cy=filterResetBtn]').click() + cy.get('@result-cards').should('have.length', 14) + }) + it('should show close button and show less filters on click', () => { + cy.get('@expandBtn').click() + cy.get('mel-datahub-filter-dropdown').should('have.length', 6) + cy.get('[data-cy=filterCloseBtn]').click() + cy.get('mel-datahub-filter-dropdown').should('have.length', 3) + }) + }) }) describe('pagination', () => { beforeEach(() => { diff --git a/apps/datahub-e2e/src/support/commands.ts b/apps/datahub-e2e/src/support/commands.ts index 9b5b3891..63f7ed7d 100644 --- a/apps/datahub-e2e/src/support/commands.ts +++ b/apps/datahub-e2e/src/support/commands.ts @@ -17,6 +17,8 @@ declare namespace Cypress { login(username?: string, password?: string, redirect?: boolean) signOut(): void clearFavorites() + selectDropdownOption(value: string): void + openDropdown(): Chainable> } } @@ -68,6 +70,29 @@ Cypress.Commands.add('signOut', () => { cy.get('a[title="Sign out"]').click() }) +// previous value should be a component +Cypress.Commands.add( + 'openDropdown', + { prevSubject: true }, + (dropdownElement) => { + cy.get('body').click('bottomLeft') // first click on the document to close other dropdowns + cy.wrap(dropdownElement).find('button').click() + return cy.get('.cdk-overlay-container').find('[role=listbox]') + } +) + +// previous value should be a component +Cypress.Commands.add( + 'selectDropdownOption', + { prevSubject: true }, + (dropdownElement, value: string) => { + cy.wrap(dropdownElement) + .openDropdown() + .find(`[data-cy-value="${value}"]`) + .click() + } +) + /** * This will most likely fail if the user is not logged in! */ diff --git a/apps/datahub/src/app/app.module.ts b/apps/datahub/src/app/app.module.ts index 63e1fa30..a0bc69bf 100644 --- a/apps/datahub/src/app/app.module.ts +++ b/apps/datahub/src/app/app.module.ts @@ -63,6 +63,7 @@ import { MelDataViewComponent } from './dataset/dataset-visualisation/data-view/ import { environment } from '../environments/environnment' import { MelModule, MelEmbeddedTranslateLoader } from '@mel-dataplatform/mel' import { MelFieldsService } from './search/service/fields.service' +import { MelDatahubDropdownRangeComponent } from './search/search-filters/mel-datahub-dropdown-range/mel-datahub-dropdown-range.component' @NgModule({ declarations: [ @@ -86,6 +87,7 @@ import { MelFieldsService } from './search/service/fields.service' DatasetVisualisationComponent, MelMapViewComponent, MelDataViewComponent, + MelDatahubDropdownRangeComponent, ], imports: [ MelModule, diff --git a/apps/datahub/src/app/dataset/dataset-header/dataset-header.component.html b/apps/datahub/src/app/dataset/dataset-header/dataset-header.component.html index c2dd70e5..f4022360 100644 --- a/apps/datahub/src/app/dataset/dataset-header/dataset-header.component.html +++ b/apps/datahub/src/app/dataset/dataset-header/dataset-header.component.html @@ -30,6 +30,7 @@

{{ record.title }}

diff --git a/apps/datahub/src/app/search/search-filters/dropdown-multiselect/dropdown-multiselect.component.html b/apps/datahub/src/app/search/search-filters/dropdown-multiselect/dropdown-multiselect.component.html index a3494706..ffde1e08 100644 --- a/apps/datahub/src/app/search/search-filters/dropdown-multiselect/dropdown-multiselect.component.html +++ b/apps/datahub/src/app/search/search-filters/dropdown-multiselect/dropdown-multiselect.component.html @@ -1,16 +1,16 @@ + + @if(overlayOpen){ + expand_less + } @else { + expand_more + } + + + + +
+
+
+
+ mel.datahub.search.filters.range.from +
+ +
+
+
+ mel.datahub.search.filters.range.to +
+ +
+ +
+
+
diff --git a/apps/datahub/src/app/search/search-filters/mel-datahub-dropdown-range/mel-datahub-dropdown-range.component.ts b/apps/datahub/src/app/search/search-filters/mel-datahub-dropdown-range/mel-datahub-dropdown-range.component.ts new file mode 100644 index 00000000..04f6475f --- /dev/null +++ b/apps/datahub/src/app/search/search-filters/mel-datahub-dropdown-range/mel-datahub-dropdown-range.component.ts @@ -0,0 +1,102 @@ +import { + CdkOverlayOrigin, + ConnectedPosition, + ScrollStrategyOptions, +} from '@angular/cdk/overlay' +import { + ChangeDetectionStrategy, + Component, + ElementRef, + EventEmitter, + Input, + Output, + ViewChild, +} from '@angular/core' +import { Choice, propagateToDocumentOnly } from 'geonetwork-ui' + +@Component({ + selector: 'mel-datahub-dropdown-range', + templateUrl: './mel-datahub-dropdown-range.component.html', + styles: ``, + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class MelDatahubDropdownRangeComponent { + lowValue = '' + highValue = '' + @Input() choices: Choice[] + @Input() title: string + @Input() selected: unknown[] = [] + @Output() selectValues = new EventEmitter() + @ViewChild('overlayOrigin') overlayOrigin: CdkOverlayOrigin + @ViewChild('overlayContainer', { read: ElementRef }) + overlayContainer: ElementRef + overlayPositions: ConnectedPosition[] = [ + { + originX: 'start', + originY: 'bottom', + overlayX: 'start', + overlayY: 'top', + offsetY: 8, + }, + { + originX: 'start', + originY: 'top', + overlayX: 'start', + overlayY: 'bottom', + offsetY: -8, + }, + ] + scrollStrategy = this.scrollStrategies.reposition() + overlayOpen = false + overlayWidth = 'auto' + overlayMaxHeight = 'none' + id = `dropdown-range-${Math.floor(Math.random() * 10000)}` + + get hasSelectedChoices() { + return this.selected.length > 0 + } + + constructor(private scrollStrategies: ScrollStrategyOptions) {} + + openOverlay() { + this.overlayWidth = + this.overlayOrigin.elementRef.nativeElement.getBoundingClientRect() + .width + 'px' + this.overlayOpen = true + } + + closeOverlay() { + this.overlayOpen = false + } + + clearSelection(event: Event) { + this.selectValues.emit([]) + this.highValue = '' + this.lowValue = '' + this.selected = [] + propagateToDocumentOnly(event) + } + + onValidate() { + const lowValue = Number(this.lowValue) * 10 + const highValue = Number(this.highValue) * 10 + this.selected = this.choices + .filter((choice) => { + const choiceNb = Number(choice.value) + if (lowValue && highValue) { + return choiceNb >= lowValue && choiceNb <= highValue + } else if (lowValue) { + return choiceNb >= lowValue + } else if (highValue) { + return choiceNb <= highValue + } + return true + }) + .map((choice) => choice.value) + if (this.selected.length === 0) { + // If no value is selected, we keep the low and high values to display nothing + this.selected.push(lowValue, highValue) + } + this.selectValues.emit(this.selected) + } +} diff --git a/apps/datahub/src/app/search/search-filters/search-filters.component.html b/apps/datahub/src/app/search/search-filters/search-filters.component.html index 053e41af..9b4c2559 100644 --- a/apps/datahub/src/app/search/search-filters/search-filters.component.html +++ b/apps/datahub/src/app/search/search-filters/search-filters.component.html @@ -1,8 +1,64 @@ -
- @for(filter of searchConfig; track filter.fieldName) { - - } +
+
+ @if(displayCount < searchConfig.length) { +
+ @for(filter of searchConfig | slice : 0 : 3; track filter.fieldName) { + + } +
+ } @else { +
+ @for(filter of searchConfig; track filter.fieldName) { + + } +
+ } +
+
+ @if(displayCount !== searchConfig.length) { + + } @else { + + } + +
diff --git a/apps/datahub/src/app/search/search-filters/search-filters.component.ts b/apps/datahub/src/app/search/search-filters/search-filters.component.ts index 75cd31b1..c9646f2a 100644 --- a/apps/datahub/src/app/search/search-filters/search-filters.component.ts +++ b/apps/datahub/src/app/search/search-filters/search-filters.component.ts @@ -1,10 +1,14 @@ import { ChangeDetectionStrategy, Component } from '@angular/core' import { marker } from '@biesbjerg/ngx-translate-extract-marker' +import { RouterFacade } from 'geonetwork-ui' +marker('mel.datahub.search.filters.topic') marker('mel.datahub.search.filters.categoryKeyword') marker('mel.datahub.search.filters.publisher') -marker('mel.datahub.search.filters.revisionYear') +marker('mel.datahub.search.filters.publicationYear') marker('mel.datahub.search.filters.license') +marker('mel.datahub.search.filters.qualityScore') +marker('mel.datahub.search.filters.territories') @Component({ selector: 'mel-datahub-search-filters', @@ -13,13 +17,32 @@ marker('mel.datahub.search.filters.license') changeDetection: ChangeDetectionStrategy.OnPush, }) export class SearchFiltersComponent { + constructor(private routerFacade: RouterFacade) {} + displayCount = 3 searchConfig = [ - 'categoryKeyword', + 'topic', 'publisher', - 'revisionYear', + 'publicationYear', 'license', + 'qualityScore', + 'territories', ].map((filter) => ({ fieldName: filter, title: `mel.datahub.search.filters.${filter}`, })) + + showAll() { + this.displayCount = + this.displayCount === this.searchConfig.length + ? 3 + : this.searchConfig.length + } + + trackByFn(item) { + return item.fieldName + } + + resetFilters() { + this.routerFacade.setSearch({}) + } } diff --git a/apps/datahub/src/app/search/search-form/search-form.component.html b/apps/datahub/src/app/search/search-form/search-form.component.html index d4b78d0f..791b0de7 100644 --- a/apps/datahub/src/app/search/search-form/search-form.component.html +++ b/apps/datahub/src/app/search/search-form/search-form.component.html @@ -6,7 +6,7 @@

mel.datahub.search.form.description

-
+
-

+

{{ record.title }}

diff --git a/resources/styles.css b/resources/styles.css index 13759634..67df2c9f 100644 --- a/resources/styles.css +++ b/resources/styles.css @@ -23,6 +23,9 @@ .mel-section-title { @apply text-[28px] font-bold text-title text-center sm:text-left; } + .mel-paragraph-title { + @apply text-[18px] font-bold text-title sm:text-left; + } .mel-badge { @apply inline-block bg-white py-1.5 px-2 rounded font-medium text-sm leading-none transition-colors; } @@ -78,7 +81,7 @@ @apply inline-flex items-center justify-center w-10 h-10 bg-primary rounded-full cursor-pointer hover:bg-primary-dark; } .mel-fuzzy-search { - @apply w-[645px] text-[17px] pointer-events-auto; + @apply w-[309px] text-[17px] pointer-events-auto; } .card-icon { @apply text-primary group-hover:text-primary-dark transition-colors transition-opacity; @@ -101,6 +104,12 @@ .pagination-btn-arrow { @apply scale-[2] font-extralight; } + .mel-simple-button { + @apply font-semibold; + } + .mel-simple-button:hover { + @apply text-primary-dark; + } } html, body { diff --git a/resources/translations/en_MEL.json b/resources/translations/en_MEL.json index 8ca0245d..2bb51b13 100644 --- a/resources/translations/en_MEL.json +++ b/resources/translations/en_MEL.json @@ -12,8 +12,19 @@ "mel.datahub.search.clear": "", "mel.datahub.search.filters.categoryKeyword": "", "mel.datahub.search.filters.license": "", + "mel.datahub.search.filters.maxValue": "", + "mel.datahub.search.filters.minValue": "", + "mel.datahub.search.filters.more": "", + "mel.datahub.search.filters.publicationYear": "", "mel.datahub.search.filters.publisher": "", - "mel.datahub.search.filters.revisionYear": "", + "mel.datahub.search.filters.qualityScore": "", + "mel.datahub.search.filters.range.from": "", + "mel.datahub.search.filters.range.to": "", + "mel.datahub.search.filters.reduce": "", + "mel.datahub.search.filters.reset": "", + "mel.datahub.search.filters.territories": "", + "mel.datahub.search.filters.topic": "", + "mel.datahub.search.filters.validate": "", "mel.datahub.search.form.description": "", "mel.datahub.search.form.title": "", "mel.datahub.search.hits.found": "", @@ -75,5 +86,7 @@ "mel.search.filter.generatedByWfs": "", "mel.searchpage.subtitle.favorites": "", "mel.tooltip.url.copy": "", - "search.filters.categoryKeyword": "" + "search.filters.categoryKeyword": "", + "search.filters.qualityScore": "", + "search.filters.territories": "" } diff --git a/resources/translations/fr_MEL.json b/resources/translations/fr_MEL.json index d051a6cb..2d841f72 100644 --- a/resources/translations/fr_MEL.json +++ b/resources/translations/fr_MEL.json @@ -12,8 +12,19 @@ "mel.datahub.search.clear": "Effacer", "mel.datahub.search.filters.categoryKeyword": "Thématique", "mel.datahub.search.filters.license": "Licence", + "mel.datahub.search.filters.maxValue": "Valeur maximale", + "mel.datahub.search.filters.minValue": "Valeur minimale", + "mel.datahub.search.filters.more": "Plus de filtres", + "mel.datahub.search.filters.publicationYear": "Date", "mel.datahub.search.filters.publisher": "Producteur", - "mel.datahub.search.filters.revisionYear": "Date", + "mel.datahub.search.filters.qualityScore": "Score de qualité", + "mel.datahub.search.filters.range.from": "De :", + "mel.datahub.search.filters.range.to": "Jusqu'à :", + "mel.datahub.search.filters.reduce": "Moins de filtres", + "mel.datahub.search.filters.reset": "Réinitialiser", + "mel.datahub.search.filters.territories": "Territoires", + "mel.datahub.search.filters.topic": "Catégories", + "mel.datahub.search.filters.validate": "Valider", "mel.datahub.search.form.description": "Vous pouvez utiliser la barre de recherche ou les différents filtres situés ci-dessous pour trouver un jeu de données plus rapidement.", "mel.datahub.search.form.title": "Trouver un jeu de données", "mel.datahub.search.hits.found": "{hits, plural, =0{Aucune correspondance.} one{1 enregistrement trouvé.} other{Ensemble des données: {hits}}}", @@ -75,5 +86,7 @@ "mel.search.filter.generatedByWfs": "généré par une API", "mel.searchpage.subtitle.favorites": "Jeux de données suivis", "mel.tooltip.url.copy": "Copier l'URL racine", - "search.filters.categoryKeyword": "Mot clé" + "search.filters.categoryKeyword": "Mot clé", + "search.filters.qualityScore": "Score de qualité", + "search.filters.territories": "Territoires" }