From c61e6e1aa53ce32b8f2930d93ed2935b563e44dc Mon Sep 17 00:00:00 2001 From: Dan Percic Date: Tue, 27 Jul 2021 21:17:24 +0300 Subject: [PATCH] filter search results by dossier --- .../file-workload.component.html | 1 - .../search-screen.component.html | 11 +- .../search-screen/search-screen.component.ts | 104 ++++++++++-------- .../popup-filter/popup-filter.component.html | 4 +- .../popup-filter/popup-filter.component.ts | 4 +- .../models/search-positions.type.ts | 6 + .../page-header/page-header.component.html | 25 +++-- .../page-header/page-header.component.ts | 8 +- .../modules/shared/services/filter.service.ts | 13 ++- apps/red-ui/src/assets/i18n/en.json | 3 + 10 files changed, 104 insertions(+), 75 deletions(-) create mode 100644 apps/red-ui/src/app/modules/shared/components/page-header/models/search-positions.type.ts diff --git a/apps/red-ui/src/app/modules/dossier/components/file-workload/file-workload.component.html b/apps/red-ui/src/app/modules/dossier/components/file-workload/file-workload.component.html index b6cbf79f2..3eaf76ea4 100644 --- a/apps/red-ui/src/app/modules/dossier/components/file-workload/file-workload.component.html +++ b/apps/red-ui/src/app/modules/dossier/components/file-workload/file-workload.component.html @@ -9,7 +9,6 @@
@@ -25,13 +26,13 @@ class="table-item" >
-
+
- {{ item.fileName }} + {{ item.filename }}
@@ -49,7 +50,7 @@ > {{ term }}. {{ 'search-screen.must-contain' | translate }}:  {{ term }} @@ -78,7 +79,7 @@
- {{ item.pages }} + {{ item.numberOfPages }}
@@ -87,7 +88,7 @@ diff --git a/apps/red-ui/src/app/modules/dossier/screens/search-screen/search-screen.component.ts b/apps/red-ui/src/app/modules/dossier/screens/search-screen/search-screen.component.ts index f705e2497..7aa2fa7d4 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/search-screen/search-screen.component.ts +++ b/apps/red-ui/src/app/modules/dossier/screens/search-screen/search-screen.component.ts @@ -2,7 +2,7 @@ import { Component, Injector, OnDestroy } from '@angular/core'; import { BaseListingComponent } from '../../../shared/base/base-listing.component'; import { MatchedDocument, SearchControllerService, SearchResult } from '@redaction/red-ui-http'; import { BehaviorSubject, Observable } from 'rxjs'; -import { debounceTime, map, switchMap, tap } from 'rxjs/operators'; +import { debounceTime, map, skip, switchMap, tap } from 'rxjs/operators'; import { ActivatedRoute, Router } from '@angular/router'; import { TableColConfig } from '../../../shared/components/table-col-name/table-col-name.component'; import { FilterService } from '../../../shared/services/filter.service'; @@ -14,16 +14,20 @@ import { FileStatusWrapper } from '../../../../models/file/file-status.wrapper'; import { LoadingService } from '../../../../services/loading.service'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { fileStatusTranslations } from '../../translations/file-status-translations'; +import { searchPositions } from '../../../shared/components/page-header/models/search-positions.type'; +import { keyChecker } from '../../../shared/components/filters/popup-filter/utils/filter-utils'; +import { DossierWrapper } from '../../../../state/model/dossier.wrapper'; +import { TranslateService } from '@ngx-translate/core'; interface ListItem { - fileName: string; - matchedDocument: MatchedDocument; - unmatched: string[] | null; - highlights: { [key: string]: string[] }; - routerLink: string; - status: string; - dossierName: string; - pages: number; + readonly dossierId: string; + readonly filename: string; + readonly unmatched: string[] | null; + readonly highlights: { [key: string]: string[] }; + readonly routerLink: string; + readonly status: string; + readonly dossierName: string; + readonly numberOfPages: number; } @Component({ @@ -32,14 +36,15 @@ interface ListItem { providers: [FilterService, SearchService, ScreenStateService, SortingService] }) export class SearchScreenComponent extends BaseListingComponent implements OnDestroy { - fileStatusTranslations = fileStatusTranslations; + readonly fileStatusTranslations = fileStatusTranslations; + readonly searchPositions = searchPositions; readonly itemSize = 85; readonly search$ = new BehaviorSubject(null); readonly searchResults$: Observable = this.search$.asObservable().pipe( switchMap(query => this._search(query)), map(searchResult => this._toMatchedDocuments(searchResult)), - map(documents => this._toListItems(documents)), + map(docs => this._toListItems(docs)), tap(result => this.screenStateService.setEntities(result)), tap(() => this._loadingService.stop()) ); @@ -58,7 +63,7 @@ export class SearchScreenComponent extends BaseListingComponent implem } ]; protected readonly _primaryKey = 'fileName'; - protected _tableHeaderLabel = _('search-screen.table-header'); + protected readonly _tableHeaderLabel = _('search-screen.table-header'); private _dossierId: string; constructor( @@ -67,38 +72,47 @@ export class SearchScreenComponent extends BaseListingComponent implem private readonly _activatedRoute: ActivatedRoute, private readonly _appStateService: AppStateService, private readonly _loadingService: LoadingService, + private readonly _translateService: TranslateService, private readonly _router: Router ) { super(_injector); + this.filterService.addFilterGroup({ + slug: 'dossiers', + label: this._translateService.instant('search-screen.filters.by-dossier'), + icon: 'red:folder', + values: this._appStateService.allDossiers.map(dossier => ({ + key: dossier.dossierId, + label: dossier.dossierName + })), + checker: keyChecker('dossierId') + }); + this.addSubscription = _activatedRoute.queryParamMap .pipe( tap(() => this._loadingService.start()), - map(value => ({ query: value.get('query'), dossierId: value.get('dossierId') })), - tap(mappedValue => this._updateValues(mappedValue)) + map(value => ({ query: value.get('query'), dossierId: value.get('dossierId') })) ) - .subscribe(); + .subscribe(mappedValue => this._updateValues(mappedValue)); this.addSubscription = this.searchService.searchForm .get('query') .valueChanges.pipe(debounceTime(300)) - .subscribe(value => this.updateNavigation({ query: value })); + .subscribe(value => this.updateNavigation(value)); + + this.addSubscription = this.filterService.filterGroups$ + .pipe(skip(1)) + .subscribe(() => this.updateNavigation(this.search$.getValue())); } setInitialConfig() { return; } - updateNavigation({ query, mustContain }: { readonly query: string; readonly mustContain?: string }) { + updateNavigation(query: string, mustContain?: string) { const newQuery = query?.replace(mustContain, `"${mustContain}"`); const queryParams = newQuery && newQuery !== '' ? { query: newQuery } : {}; - const queryParamsHandling = this._dossierId ? 'merge' : ''; - this._router - .navigate([], { - queryParams, - queryParamsHandling - }) - .then(); + this._router.navigate([], { queryParams }).then(); } private _search(query: string): Observable { @@ -112,6 +126,7 @@ export class SearchScreenComponent extends BaseListingComponent implem } private _updateValues({ query, dossierId }: { readonly query: string; readonly dossierId: string }) { + if (dossierId) this.filterService.toggleFilter('dossiers', dossierId); this._dossierId = dossierId; this.searchService.searchValue = query; this.search$.next(query); @@ -121,34 +136,31 @@ export class SearchScreenComponent extends BaseListingComponent implem return this._appStateService.getFileById(dossierId, fileId); } - private _getDossierWrapper(dossierId: string) { + private _getDossierWrapper(dossierId: string): DossierWrapper { return this._appStateService.getDossierById(dossierId); } - private _toMatchedDocuments({ matchedDocuments }: SearchResult) { + private _toMatchedDocuments({ matchedDocuments }: SearchResult): MatchedDocument[] { return matchedDocuments.filter(doc => doc.score > 0 && doc.matchedTerms.length > 0); } - private _toListItems(matchedDocuments: MatchedDocument[]) { - return matchedDocuments - .map(document => { - const fileStatus = this._getFileWrapper(document.dossierId, document.fileId); - if (!fileStatus) { - return undefined; - } + private _toListItems(matchedDocuments: MatchedDocument[]): ListItem[] { + return matchedDocuments.map(document => this._toListItem(document)).filter(value => value); + } - const { dossierId, dossierName } = this._getDossierWrapper(document.dossierId); - return { - matchedDocument: document, - unmatched: document.unmatchedTerms.length ? document.unmatchedTerms : null, - highlights: document.highlights, - status: fileStatus.status, - pages: fileStatus.numberOfPages, - dossierName: dossierName, - fileName: fileStatus.filename, - routerLink: `/main/dossiers/${dossierId}/file/${fileStatus.fileId}` - } as ListItem; - }) - .filter(value => value); + private _toListItem({ dossierId, fileId, unmatchedTerms, highlights }: MatchedDocument): ListItem { + const fileWrapper = this._getFileWrapper(dossierId, fileId); + if (!fileWrapper) return undefined; + + return { + dossierId, + unmatched: unmatchedTerms || null, + highlights, + status, + numberOfPages: fileWrapper.numberOfPages, + dossierName: this._getDossierWrapper(dossierId).dossierName, + filename: fileWrapper.filename, + routerLink: `/main/dossiers/${dossierId}/file/${fileId}` + }; } } diff --git a/apps/red-ui/src/app/modules/shared/components/filters/popup-filter/popup-filter.component.html b/apps/red-ui/src/app/modules/shared/components/filters/popup-filter/popup-filter.component.html index ab78f2597..7d27c3f02 100644 --- a/apps/red-ui/src/app/modules/shared/components/filters/popup-filter/popup-filter.component.html +++ b/apps/red-ui/src/app/modules/shared/components/filters/popup-filter/popup-filter.component.html @@ -1,5 +1,5 @@ f.checked || f.indeterminate); diff --git a/apps/red-ui/src/app/modules/shared/components/page-header/models/search-positions.type.ts b/apps/red-ui/src/app/modules/shared/components/page-header/models/search-positions.type.ts new file mode 100644 index 000000000..7333d62ac --- /dev/null +++ b/apps/red-ui/src/app/modules/shared/components/page-header/models/search-positions.type.ts @@ -0,0 +1,6 @@ +export const searchPositions = { + beforeFilters: 'beforeFilters', + afterFilters: 'afterFilters' +} as const; + +export type SearchPosition = keyof typeof searchPositions; diff --git a/apps/red-ui/src/app/modules/shared/components/page-header/page-header.component.html b/apps/red-ui/src/app/modules/shared/components/page-header/page-header.component.html index 696c58c36..0ce5bf65d 100644 --- a/apps/red-ui/src/app/modules/shared/components/page-header/page-header.component.html +++ b/apps/red-ui/src/app/modules/shared/components/page-header/page-header.component.html @@ -1,8 +1,10 @@ + + + + diff --git a/apps/red-ui/src/app/modules/shared/components/page-header/page-header.component.ts b/apps/red-ui/src/app/modules/shared/components/page-header/page-header.component.ts index a50b3dceb..4f25fadde 100644 --- a/apps/red-ui/src/app/modules/shared/components/page-header/page-header.component.ts +++ b/apps/red-ui/src/app/modules/shared/components/page-header/page-header.component.ts @@ -5,6 +5,7 @@ import { FilterService } from '@shared/services/filter.service'; import { SearchService } from '@shared/services/search.service'; import { distinctUntilChanged, map } from 'rxjs/operators'; import { combineLatest, Observable, of } from 'rxjs'; +import { SearchPosition, searchPositions } from '@shared/components/page-header/models/search-positions.type'; @Component({ selector: 'redaction-page-header', @@ -12,12 +13,15 @@ import { combineLatest, Observable, of } from 'rxjs'; styleUrls: ['./page-header.component.scss'] }) export class PageHeaderComponent { + readonly searchPositions = searchPositions; + @Input() pageLabel: string; @Input() showCloseButton: boolean; @Input() actionConfigs: ActionConfig[]; @Input() buttonConfigs: ButtonConfig[]; @Input() searchPlaceholder: string; @Input() searchWidth: number | 'full'; + @Input() searchPosition: SearchPosition = this.searchPositions.afterFilters; readonly filters$ = this.filterService?.filterGroups$.pipe(map(all => all.filter(f => f.icon))); readonly showResetFilters$ = this._showResetFilters$; @@ -37,10 +41,6 @@ export class PageHeaderComponent { ); } - get computedWidth() { - return this.searchWidth === 'full' ? '100%' : `${this.searchWidth}px`; - } - resetFilters(): void { this.filterService.reset(); this.searchService.reset(); diff --git a/apps/red-ui/src/app/modules/shared/services/filter.service.ts b/apps/red-ui/src/app/modules/shared/services/filter.service.ts index 73fd37adc..4dc354234 100644 --- a/apps/red-ui/src/app/modules/shared/services/filter.service.ts +++ b/apps/red-ui/src/app/modules/shared/services/filter.service.ts @@ -2,14 +2,17 @@ import { Injectable } from '@angular/core'; import { FilterModel } from '@shared/components/filters/popup-filter/model/filter.model'; import { processFilters } from '@shared/components/filters/popup-filter/utils/filter-utils'; import { FilterGroup } from '@shared/components/filters/popup-filter/model/filter-wrapper.model'; -import { BehaviorSubject, Observable } from 'rxjs'; -import { distinctUntilChanged, map, switchMap } from 'rxjs/operators'; +import { BehaviorSubject, Observable, Subject } from 'rxjs'; +import { distinctUntilChanged, map, startWith, switchMap } from 'rxjs/operators'; @Injectable() export class FilterService { private readonly _filterGroups$ = new BehaviorSubject([]); - private readonly _refresh$ = new BehaviorSubject(null); - readonly filterGroups$ = this._refresh$.pipe(switchMap(() => this._filterGroups$.asObservable())); + private readonly _refresh$ = new Subject(); + readonly filterGroups$ = this._refresh$.pipe( + startWith(''), + switchMap(() => this._filterGroups$.asObservable()) + ); readonly showResetFilters$ = this._showResetFilters$; get filterGroups(): FilterGroup[] { @@ -25,7 +28,7 @@ export class FilterService { } refresh(): void { - this._refresh$.next(null); + this._refresh$.next(); } toggleFilter(slug: string, key: string) { diff --git a/apps/red-ui/src/assets/i18n/en.json b/apps/red-ui/src/assets/i18n/en.json index 465e6649d..1bc08575c 100644 --- a/apps/red-ui/src/assets/i18n/en.json +++ b/apps/red-ui/src/assets/i18n/en.json @@ -1205,6 +1205,9 @@ "pages": "Pages", "status": "Status" }, + "filters": { + "by-dossier": "Filter by Dossier" + }, "missing": "Missing", "must-contain": "Must contain", "no-data": "Please enter a keyword in the search input to look for documents or document content.",