Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Editor: Add a search filters summary component #1037

Draft
wants to merge 7 commits into
base: me-date-filter
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<div *ngIf="(fieldValues$ | async)?.length > 0">
<div
*ngIf="fieldType === 'dateRange'; else valuesBadge"
class="flex flex-row items-center gap-2"
>
<span class="text-gray-800" translate
>search.filters.summaryLabel.{{ fieldName }}</span
>
<gn-ui-badge
*ngFor="let fieldValue of dateRange$ | async"
[style.--gn-ui-badge-text-color]="'black'"
[style.--gn-ui-badge-background-color]="'#F2F2F2'"
[style.--gn-ui-badge-font-weight]="'700'"
[removable]="true"
(badgeRemoveClicked)="removeFilterValue(fieldValue)"
>{{ fieldValue['start'] | date: 'dd.MM.yyyy' }} -
{{ fieldValue['end'] | date: 'dd.MM.yyyy' }}</gn-ui-badge
>
</div>
<ng-template #valuesBadge>
<div class="flex flex-row items-center gap-2">
<span class="text-gray-800" translate
>search.filters.summaryLabel.{{ fieldName }}</span
>
<gn-ui-badge
*ngFor="let fieldValue of values$ | async"
[style.--gn-ui-badge-text-color]="'black'"
[style.--gn-ui-badge-background-color]="'#F2F2F2'"
[style.--gn-ui-badge-font-weight]="'700'"
[removable]="true"
(badgeRemoveClicked)="removeFilterValue(fieldValue)"
>{{ fieldValue }}</gn-ui-badge
>
</div>
</ng-template>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'

import { SearchFiltersSummaryItemComponent } from './search-filters-summary-item.component'

describe('SearchFiltersSummaryComponent', () => {
let component: SearchFiltersSummaryItemComponent
let fixture: ComponentFixture<SearchFiltersSummaryItemComponent>

beforeEach(() => {
TestBed.configureTestingModule({
imports: [SearchFiltersSummaryItemComponent],
})
fixture = TestBed.createComponent(SearchFiltersSummaryItemComponent)
component = fixture.componentInstance
fixture.detectChanges()
})

it('should create', () => {
expect(component).toBeTruthy()
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'
import { CommonModule } from '@angular/common'
import {
FieldsService,
FieldType,
FieldValue,
FieldValues,
SearchFacade,
SearchService,
} from '@geonetwork-ui/feature/search'
import {
catchError,
filter,
firstValueFrom,
map,
Observable,
startWith,
switchMap,
tap,
} from 'rxjs'
import { BadgeComponent } from '@geonetwork-ui/ui/inputs'
import { TranslateModule } from '@ngx-translate/core'
import { DateRange } from '@geonetwork-ui/api/repository'

@Component({
selector: 'md-editor-search-filters-summary-item',
standalone: true,
imports: [CommonModule, TranslateModule, BadgeComponent],
templateUrl: './search-filters-summary-item.component.html',
styleUrls: ['./search-filters-summary-item.component.css'],
})
export class SearchFiltersSummaryItemComponent implements OnInit {
@Input() fieldName: string
fieldType: FieldType

fieldValues$ = this.searchFacade.searchFilters$.pipe(
switchMap((filters) =>
this.fieldsService.readFieldValuesFromFilters(filters)
),
tap((fieldValues) => console.log(fieldValues)),
map((fieldValues) =>
Array.isArray(fieldValues[this.fieldName]) //TODO: handle date ranges as arrays everywhere?
? fieldValues[this.fieldName]
: [fieldValues[this.fieldName]]
),
tap((fieldValues) => console.log(fieldValues))
// startWith([]),
// catchError(() => of([]))
) as Observable<FieldValue[] | DateRange[]>

dateRange$ = this.fieldValues$.pipe(
filter(() => this.fieldType === 'dateRange'),
map((fieldValues) => fieldValues as DateRange[])
)

values$ = this.fieldValues$.pipe(
filter(() => this.fieldType === 'values'),
map((fieldValues) => fieldValues as FieldValue[])
)

constructor(
private searchFacade: SearchFacade,
private searchService: SearchService,
private fieldsService: FieldsService
) {}

ngOnInit() {
this.fieldType = this.fieldsService.getFieldType(this.fieldName)
}

async removeFilterValue(fieldValue: FieldValue | DateRange) {
const currentFieldValues: (FieldValue | DateRange)[] = await firstValueFrom(
this.fieldValues$
)
const updatedFieldValues = currentFieldValues.filter(
(value: string | DateRange) => value !== fieldValue
)
this.fieldsService
.buildFiltersFromFieldValues({
[this.fieldName]: updatedFieldValues as FieldValue[],
})
.subscribe((filters) => this.searchService.updateFilters(filters))
Comment on lines +72 to +82
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how about directly modifying the current filters instead of going from filters to values and then values to filters? simply deleting the corresponding key in the filters should be enough

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<div
class="flex flex-row py-3 px-4 gap-4 overflow-hidden grow border-[1px] border-gray-200"
*ngIf="searchFilterActive$ | async"
>
<div class="flex flex-row flex-wrap gap-2">
<md-editor-search-filters-summary-item
*ngFor="let field of searchFields; let i = index"
[fieldName]="field"
></md-editor-search-filters-summary-item>
</div>
<button
class="bg-white text-black font-bold ml-auto"
(click)="clearFilters()"
>
{{ 'search.filters.clear' | translate }}
</button>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { SearchFiltersSummaryComponent } from './search-filters-summary.component'

describe('SearchFiltersSummaryComponent', () => {
let component: SearchFiltersSummaryComponent
let fixture: ComponentFixture<SearchFiltersSummaryComponent>

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [SearchFiltersSummaryComponent],
}).compileComponents()

fixture = TestBed.createComponent(SearchFiltersSummaryComponent)
component = fixture.componentInstance
fixture.detectChanges()
})

it('should create', () => {
expect(component).toBeTruthy()
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Component, Input } from '@angular/core'
import { CommonModule } from '@angular/common'
import { SearchFacade, SearchService } from '@geonetwork-ui/feature/search'
import { map } from 'rxjs'
import { SearchFiltersSummaryItemComponent } from '../search-filters-summary-item/search-filters-summary-item.component'
import { TranslateModule } from '@ngx-translate/core'

@Component({
selector: 'md-editor-search-filters-summary',
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this can maybe go in the feature-search lib ?

imports: [CommonModule, SearchFiltersSummaryItemComponent, TranslateModule],
templateUrl: './search-filters-summary.component.html',
styleUrls: ['./search-filters-summary.component.css'],
standalone: true,
})
export class SearchFiltersSummaryComponent {
@Input() searchFields: string[] = []

searchFilterActive$ = this.searchFacade.searchFilters$.pipe(
map((filters) => this.hasNonEmptyValues(filters))
)
Comment on lines +16 to +20
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering if instead of taking a list of filters as input we can simply listen for the search filters and show everyone of them. This means that if another filter (which does not appear in the advanced filters above) is active then it will also show up. What do you think?


constructor(
private searchFacade: SearchFacade,
private searchService: SearchService
) {}

hasNonEmptyValues(filters: any): boolean {
return Object.values(filters).some(
(value) =>
value !== undefined &&
(typeof value !== 'object' ||
(typeof value === 'object' && Object.keys(value).length > 0))
)
}

clearFilters() {
this.searchService.setFilters({})
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
<div
class="flex flex-row py-3 px-4 gap-4 shadow-md shadow-gray-300 border-[1px] border-gray-200 overflow-hidden rounded bg-white grow mx-[32px] my-[16px] text-sm"
class="rounded bg-white shadow-md shadow-gray-300 border-[1px] border-gray-200 mx-[32px] my-[16px] text-sm"
>
<mat-icon class="material-symbols-outlined">filter_list</mat-icon>
<gn-ui-filter-dropdown
*ngFor="let filter of searchConfig; let i = index"
[fieldName]="filter.fieldName"
[title]="filter.title | translate"
[style.--gn-ui-button-height]="'32px'"
[style.--gn-ui-multiselect-counter-text-color]="'var(--color-primary)'"
[style.--gn-ui-multiselect-counter-background-color]="'white'"
></gn-ui-filter-dropdown>
<div
class="flex flex-row py-3 px-4 gap-4 overflow-hidden grow border-[1px] border-gray-200"
>
<mat-icon class="material-symbols-outlined">filter_list</mat-icon>
<gn-ui-filter-dropdown
*ngFor="let filter of searchConfig; let i = index"
[fieldName]="filter.fieldName"
[title]="filter.title | translate"
[style.--gn-ui-button-height]="'32px'"
[style.--gn-ui-multiselect-counter-text-color]="'var(--color-primary)'"
[style.--gn-ui-multiselect-counter-background-color]="'white'"
></gn-ui-filter-dropdown>
</div>
<md-editor-search-filters-summary
[searchFields]="searchFields"
></md-editor-search-filters-summary>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,18 @@ import { CommonModule } from '@angular/common'
import { TranslateModule } from '@ngx-translate/core'
import { FeatureSearchModule } from '@geonetwork-ui/feature/search'
import { MatIconModule } from '@angular/material/icon'
import { SearchFiltersSummaryComponent } from '../search-filters-summary/search-filters-summary.component'

@Component({
selector: 'md-editor-search-filters',
standalone: true,
imports: [CommonModule, TranslateModule, FeatureSearchModule, MatIconModule],
imports: [
CommonModule,
TranslateModule,
FeatureSearchModule,
MatIconModule,
SearchFiltersSummaryComponent,
],
templateUrl: './search-filters.component.html',
styleUrls: ['./search-filters.component.css'],
})
Expand Down
3 changes: 3 additions & 0 deletions libs/feature/search/src/lib/utils/service/fields.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ marker('search.filters.publisherOrg')
marker('search.filters.user')
marker('search.filters.changeDate')

marker('search.filters.summaryLabel.user')
marker('search.filters.summaryLabel.changeDate')
Comment on lines +42 to +43
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how about marking labels for all fields? It might make more sense taken from outside I think. Another approach would be to use the field normal label if a summaryLabel is not available.


@Injectable({
providedIn: 'root',
})
Expand Down
2 changes: 1 addition & 1 deletion libs/ui/inputs/src/lib/badge/badge.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
--gn-ui-button-width: 1.4em;
--gn-ui-button-height: 1.4em;
--gn-ui-button-rounded: 1.4em;
--gn-ui-button-background: white;
--gn-ui-button-background: var(--gn-ui-badge-background-color, white);
"
>
<mat-icon class="material-symbols-outlined leading-[1.1]">close</mat-icon>
Expand Down
3 changes: 2 additions & 1 deletion tailwind.base.css
Original file line number Diff line number Diff line change
Expand Up @@ -143,12 +143,13 @@
.gn-ui-badge {
--rounded: var(--gn-ui-badge-rounded, 0.25em);
--padding: var(--gn-ui-badge-padding, 0.375em 0.75em);
--font-weight: var(--gn-ui-badge-font-weight, 500);
--text-size: var(--gn-ui-badge-text-size, 0.875em);
--text-color: var(--gn-ui-badge-text-color, var(--color-gray-50));
--background-color: var(--gn-ui-badge-background-color, black);
--opacity: var(--gn-ui-badge-opacity, 0.7);
@apply opacity-[--opacity] p-[--padding] rounded-[--rounded]
font-medium text-[length:--text-size] text-[color:--text-color] bg-[color:--background-color] flex justify-center items-center content-center;
font-[--font-weight] text-[length:--text-size] text-[color:--text-color] bg-[color:--background-color] flex justify-center items-center content-center;
}
/* makes sure icons will not make the badges grow vertically; also make size proportional */
.gn-ui-badge mat-icon.mat-icon {
Expand Down
2 changes: 2 additions & 0 deletions translations/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,8 @@
"search.filters.representationType": "Repräsentationstyp",
"search.filters.resourceType": "Ressourcentyp",
"search.filters.standard": "Standard",
"search.filters.summaryLabel.changeDate": "Geändert am: ",
"search.filters.summaryLabel.user": "Geändert von: ",
"search.filters.title": "Ergebnisse filtern",
"search.filters.topic": "Themen",
"search.filters.useSpatialFilter": "Zuerst Datensätze im Interessenbereich anzeigen",
Expand Down
2 changes: 2 additions & 0 deletions translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,8 @@
"search.filters.representationType": "Representation type",
"search.filters.resourceType": "Resource type",
"search.filters.standard": "Standard",
"search.filters.summaryLabel.changeDate": "Modified on: ",
"search.filters.summaryLabel.user": "Modified by: ",
"search.filters.title": "Filter your results",
"search.filters.topic": "Topics",
"search.filters.useSpatialFilter": "Show records in the area of interest first",
Expand Down
2 changes: 2 additions & 0 deletions translations/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,8 @@
"search.filters.representationType": "",
"search.filters.resourceType": "",
"search.filters.standard": "",
"search.filters.summaryLabel.changeDate": "",
"search.filters.summaryLabel.user": "",
"search.filters.title": "",
"search.filters.topic": "",
"search.filters.useSpatialFilter": "",
Expand Down
2 changes: 2 additions & 0 deletions translations/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,8 @@
"search.filters.representationType": "Type de représentation",
"search.filters.resourceType": "Type de ressource",
"search.filters.standard": "Standard",
"search.filters.summaryLabel.changeDate": "Modifiée le : ",
"search.filters.summaryLabel.user": "Modifiée par : ",
"search.filters.title": "Affiner votre recherche",
"search.filters.topic": "Thèmes",
"search.filters.useSpatialFilter": "Mettre en avant les résultats sur la zone d'intérêt",
Expand Down
2 changes: 2 additions & 0 deletions translations/it.json
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,8 @@
"search.filters.representationType": "Tipo di rappresentazione",
"search.filters.resourceType": "Tipo di risorsa",
"search.filters.standard": "Standard",
"search.filters.summaryLabel.changeDate": "",
"search.filters.summaryLabel.user": "",
"search.filters.title": "Affina la sua ricerca",
"search.filters.topic": "Argomenti",
"search.filters.useSpatialFilter": "Evidenzia i risultati nell'area di interesse",
Expand Down
2 changes: 2 additions & 0 deletions translations/nl.json
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,8 @@
"search.filters.representationType": "",
"search.filters.resourceType": "",
"search.filters.standard": "",
"search.filters.summaryLabel.changeDate": "",
"search.filters.summaryLabel.user": "",
"search.filters.title": "",
"search.filters.topic": "",
"search.filters.useSpatialFilter": "",
Expand Down
2 changes: 2 additions & 0 deletions translations/pt.json
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,8 @@
"search.filters.representationType": "",
"search.filters.resourceType": "",
"search.filters.standard": "",
"search.filters.summaryLabel.changeDate": "",
"search.filters.summaryLabel.user": "",
"search.filters.title": "",
"search.filters.topic": "",
"search.filters.useSpatialFilter": "",
Expand Down
2 changes: 2 additions & 0 deletions translations/sk.json
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,8 @@
"search.filters.representationType": "Typ reprezentácie",
"search.filters.resourceType": "Typ zdroja",
"search.filters.standard": "Štandard",
"search.filters.summaryLabel.changeDate": "",
"search.filters.summaryLabel.user": "",
"search.filters.title": "Filtrovanie výsledkov",
"search.filters.topic": "Témy",
"search.filters.useSpatialFilter": "Najskôr zobraziť záznamy v oblasti záujmu",
Expand Down
Loading