From 5387fef4621fa3a9bd17198bc4912646c7cc78a6 Mon Sep 17 00:00:00 2001 From: Dan Percic Date: Sat, 25 Sep 2021 21:03:40 +0300 Subject: [PATCH] move filters to classes, add searchKey to entities --- .../app/models/file/file-status.wrapper.ts | 5 +- ...ttributes-csv-import-dialog.component.html | 2 +- .../dossier-details.component.html | 2 +- .../file-workload/file-workload.component.ts | 6 +- .../type-filter/type-filter.component.html | 24 ++-- .../type-filter/type-filter.component.ts | 6 +- ...dit-dossier-deleted-documents.component.ts | 4 +- .../dossier-listing-screen.component.html | 4 +- .../dossier-listing-screen.component.ts | 98 +++++++++------ .../dossier-overview-screen.component.ts | 114 ++++++++++-------- .../file-preview-screen.component.ts | 4 +- .../search-screen/search-screen.component.ts | 51 ++++---- .../services/annotation-processing.service.ts | 55 ++++----- .../page-header/page-header.component.ts | 4 +- .../simple-doughnut-chart.component.ts | 2 +- apps/red-ui/src/app/utils/filter-utils.ts | 34 +++--- .../utils/sorters/redaction-filter-sorter.ts | 2 +- 17 files changed, 226 insertions(+), 191 deletions(-) diff --git a/apps/red-ui/src/app/models/file/file-status.wrapper.ts b/apps/red-ui/src/app/models/file/file-status.wrapper.ts index 16abefbb2..220bc522a 100644 --- a/apps/red-ui/src/app/models/file/file-status.wrapper.ts +++ b/apps/red-ui/src/app/models/file/file-status.wrapper.ts @@ -64,7 +64,6 @@ export class FileStatusWrapper implements FileStatus, IListable { } } - readonly excludedPagesCount = this.excludedPages?.length ?? 0; readonly statusSort = StatusSorter[this.status]; readonly pages = this._pages; readonly cacheIdentifier = btoa(this.lastUploaded + this.lastOCRTime); @@ -88,6 +87,10 @@ export class FileStatusWrapper implements FileStatus, IListable { return this.fileId; } + get searchKey(): string { + return this.filename; + } + private get _pages() { if (this.fileStatus.status === 'ERROR') { return -1; diff --git a/apps/red-ui/src/app/modules/admin/dialogs/file-attributes-csv-import-dialog/file-attributes-csv-import-dialog.component.html b/apps/red-ui/src/app/modules/admin/dialogs/file-attributes-csv-import-dialog/file-attributes-csv-import-dialog.component.html index 1fbc14715..0357d5eb5 100644 --- a/apps/red-ui/src/app/modules/admin/dialogs/file-attributes-csv-import-dialog/file-attributes-csv-import-dialog.component.html +++ b/apps/red-ui/src/app/modules/admin/dialogs/file-attributes-csv-import-dialog/file-attributes-csv-import-dialog.component.html @@ -100,7 +100,7 @@ (click)="toggleFieldActive(field)" (mouseenter)="setHoveredColumn(field.csvColumn)" (mouseleave)="setHoveredColumn()" - *ngFor="let field of sortedDisplayedEntities$ | async; trackBy: trackByPrimaryKey" + *ngFor="let field of sortedDisplayedEntities$ | async" class="csv-header-pill-wrapper" >
diff --git a/apps/red-ui/src/app/modules/dossier/components/dossier-details/dossier-details.component.html b/apps/red-ui/src/app/modules/dossier/components/dossier-details/dossier-details.component.html index 53e587bae..056f88103 100644 --- a/apps/red-ui/src/app/modules/dossier/components/dossier-details/dossier-details.component.html +++ b/apps/red-ui/src/app/modules/dossier/components/dossier-details/dossier-details.component.html @@ -50,7 +50,7 @@
diff --git a/apps/red-ui/src/app/modules/dossier/components/file-workload/file-workload.component.ts b/apps/red-ui/src/app/modules/dossier/components/file-workload/file-workload.component.ts index e58b9455b..3dc3b67b9 100644 --- a/apps/red-ui/src/app/modules/dossier/components/file-workload/file-workload.component.ts +++ b/apps/red-ui/src/app/modules/dossier/components/file-workload/file-workload.component.ts @@ -3,7 +3,7 @@ import { AnnotationWrapper } from '@models/file/annotation.wrapper'; import { AnnotationProcessingService } from '../../services/annotation-processing.service'; import { MatDialogRef, MatDialogState } from '@angular/material/dialog'; import scrollIntoView from 'scroll-into-view-if-needed'; -import { CircleButtonTypes, Debounce, FilterService, IconButtonTypes, IqserEventTarget, NestedFilter } from '@iqser/common-ui'; +import { CircleButtonTypes, Debounce, FilterService, IconButtonTypes, INestedFilter, IqserEventTarget } from '@iqser/common-ui'; import { FileDataModel } from '@models/file/file-data.model'; import { PermissionsService } from '@services/permissions.service'; import { WebViewerInstance } from '@pdftron/webviewer'; @@ -122,8 +122,8 @@ export class FileWorkloadComponent { private _filterAnnotations( annotations: AnnotationWrapper[], - primary: NestedFilter[], - secondary: NestedFilter[] = [] + primary: INestedFilter[], + secondary: INestedFilter[] = [] ): Map { if (!primary) { return; diff --git a/apps/red-ui/src/app/modules/dossier/components/type-filter/type-filter.component.html b/apps/red-ui/src/app/modules/dossier/components/type-filter/type-filter.component.html index fc518b945..8de42a9c4 100644 --- a/apps/red-ui/src/app/modules/dossier/components/type-filter/type-filter.component.html +++ b/apps/red-ui/src/app/modules/dossier/components/type-filter/type-filter.component.html @@ -1,57 +1,57 @@ - + - + - + -
+
diff --git a/apps/red-ui/src/app/modules/dossier/components/type-filter/type-filter.component.ts b/apps/red-ui/src/app/modules/dossier/components/type-filter/type-filter.component.ts index 3de66f8b4..9d2866f9d 100644 --- a/apps/red-ui/src/app/modules/dossier/components/type-filter/type-filter.component.ts +++ b/apps/red-ui/src/app/modules/dossier/components/type-filter/type-filter.component.ts @@ -1,6 +1,6 @@ import { Component, Input, OnInit } from '@angular/core'; import { AppStateService } from '@state/app-state.service'; -import { NestedFilter } from '@iqser/common-ui'; +import { INestedFilter } from '@iqser/common-ui'; @Component({ selector: 'redaction-type-filter', @@ -8,7 +8,7 @@ import { NestedFilter } from '@iqser/common-ui'; styleUrls: ['./type-filter.component.scss'] }) export class TypeFilterComponent implements OnInit { - @Input() filter: NestedFilter; + @Input() filter: INestedFilter; dictionaryColor: string; @@ -36,6 +36,6 @@ export class TypeFilterComponent implements OnInit { needsAnalysis = (key: string) => this._needsAnalysisKeys.includes(key); ngOnInit(): void { - this.dictionaryColor = this._appStateService.getDictionaryColor(this.filter.key); + this.dictionaryColor = this._appStateService.getDictionaryColor(this.filter.id); } } diff --git a/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/deleted-documents/edit-dossier-deleted-documents.component.ts b/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/deleted-documents/edit-dossier-deleted-documents.component.ts index 06aa72cd9..b742ff20f 100644 --- a/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/deleted-documents/edit-dossier-deleted-documents.component.ts +++ b/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/deleted-documents/edit-dossier-deleted-documents.component.ts @@ -1,6 +1,6 @@ import { Component, EventEmitter, forwardRef, Injector, Input, OnInit, Output, TemplateRef, ViewChild } from '@angular/core'; import { EditDossierSectionInterface } from '../edit-dossier-section.interface'; -import { Dossier } from '../../../../../state/model/dossier'; +import { Dossier } from '@state/model/dossier'; import { CircleButtonTypes, DefaultListingServices, @@ -49,7 +49,6 @@ export class EditDossierDeletedDocumentsComponent extends ListingComponent; @ViewChild('deletedDateTemplate', { static: true }) deletedDateTemplate: TemplateRef; @ViewChild('restoreDateTemplate', { static: true }) restoreDateTemplate: TemplateRef; - protected readonly _primaryKey = 'fileId'; constructor( protected readonly _injector: Injector, @@ -167,6 +166,7 @@ export class EditDossierDeletedDocumentsComponent extends ListingComponent
- {{ dossier.dossierTemplateName }} + {{ getDossierTemplateNameFor(dossier.dossierTemplateId) }}
@@ -53,7 +53,7 @@
- {{ dossier.memberCount }} + {{ dossier.memberIds.length }}
diff --git a/apps/red-ui/src/app/modules/dossier/screens/dossier-listing-screen/dossier-listing-screen.component.ts b/apps/red-ui/src/app/modules/dossier/screens/dossier-listing-screen/dossier-listing-screen.component.ts index 94117041d..02e04ed36 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/dossier-listing-screen/dossier-listing-screen.component.ts +++ b/apps/red-ui/src/app/modules/dossier/screens/dossier-listing-screen/dossier-listing-screen.component.ts @@ -16,7 +16,15 @@ import { DossiersDialogService } from '../../services/dossiers-dialog.service'; import { OnAttach, OnDetach } from '@utils/custom-route-reuse.strategy'; import { UserPreferenceService } from '@services/user-preference.service'; import { ButtonConfig } from '@shared/components/page-header/models/button-config.model'; -import { DefaultListingServices, keyChecker, ListingComponent, NestedFilter, TableColumnConfig, TableComponent } from '@iqser/common-ui'; +import { + DefaultListingServices, + INestedFilter, + keyChecker, + ListingComponent, + NestedFilter, + TableColumnConfig, + TableComponent +} from '@iqser/common-ui'; import { workloadTranslations } from '../../translations/workload-translations'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { fileStatusTranslations } from '../../translations/file-status-translations'; @@ -46,11 +54,10 @@ export class DossierListingScreenComponent tableColumnConfigs: TableColumnConfig[]; dossiersChartData: DoughnutChartConfig[] = []; documentsChartData: DoughnutChartConfig[] = []; - @ViewChild('nameTemplate', { static: true }) nameTemplate: TemplateRef; - @ViewChild('needsWorkTemplate', { static: true }) needsWorkTemplate: TemplateRef; - @ViewChild('ownerTemplate', { static: true }) ownerTemplate: TemplateRef; - @ViewChild('statusTemplate', { static: true }) statusTemplate: TemplateRef; - protected readonly _primaryKey = 'dossierName'; + @ViewChild('nameTemplate', { static: true }) nameTemplate: TemplateRef; + @ViewChild('needsWorkTemplate', { static: true }) needsWorkTemplate: TemplateRef; + @ViewChild('ownerTemplate', { static: true }) ownerTemplate: TemplateRef; + @ViewChild('statusTemplate', { static: true }) statusTemplate: TemplateRef; private _lastScrolledIndex: number; @ViewChild('needsWorkFilterTemplate', { read: TemplateRef, @@ -85,6 +92,10 @@ export class DossierListingScreenComponent routerLinkFn = (dossier: Dossier) => ['/main/dossiers/' + dossier.id]; + getDossierTemplateNameFor(dossierTemplateId: string): string { + return this._appStateService.getDossierTemplateById(dossierTemplateId).name; + } + ngOnInit(): void { this._configureTableColumns(); this.calculateData(); @@ -156,7 +167,7 @@ export class DossierListingScreenComponent this.tableColumnConfigs = [ { label: _('dossier-listing.table-col-names.name'), - sortByKey: 'dossierName', + sortByKey: 'searchKey', template: this.nameTemplate, width: '2fr' }, @@ -214,23 +225,29 @@ export class DossierListingScreenComponent allDistinctDossierTemplates.add(entry.dossierTemplateId); }); - const statusFilters = [...allDistinctFileStatus].map(status => ({ - key: status, - label: this._translateService.instant(fileStatusTranslations[status]) - })); + const statusFilters = [...allDistinctFileStatus].map( + status => + new NestedFilter({ + id: status, + label: this._translateService.instant(fileStatusTranslations[status]) + }) + ); this.filterService.addFilterGroup({ slug: 'statusFilters', label: this._translateService.instant('filters.status'), icon: 'red:status', - filters: statusFilters.sort(StatusSorter.byStatus), + filters: statusFilters.sort((a, b) => StatusSorter[a.id] - StatusSorter[b.id]), checker: dossierStatusChecker }); - const peopleFilters = [...allDistinctPeople].map(userId => ({ - key: userId, - label: this._userService.getNameForId(userId) - })); + const peopleFilters = [...allDistinctPeople].map( + userId => + new NestedFilter({ + id: userId, + label: this._userService.getNameForId(userId) + }) + ); this.filterService.addFilterGroup({ slug: 'peopleFilters', @@ -240,25 +257,31 @@ export class DossierListingScreenComponent checker: dossierMemberChecker }); - const needsWorkFilters = [...allDistinctNeedsWork].map(type => ({ - key: type, - label: workloadTranslations[type] - })); + const needsWorkFilters = [...allDistinctNeedsWork].map( + type => + new NestedFilter({ + id: type, + label: workloadTranslations[type] + }) + ); this.filterService.addFilterGroup({ slug: 'needsWorkFilters', label: this._translateService.instant('filters.needs-work'), icon: 'red:needs-work', filterTemplate: this._needsWorkFilterTemplate, - filters: needsWorkFilters.sort(RedactionFilterSorter.byKey), + filters: needsWorkFilters.sort((a, b) => RedactionFilterSorter[a.id] - RedactionFilterSorter[b.id]), checker: annotationFilterChecker, matchAll: true }); - const dossierTemplateFilters = [...allDistinctDossierTemplates].map(id => ({ - key: id, - label: this._appStateService.getDossierTemplateById(id).name - })); + const dossierTemplateFilters = [...allDistinctDossierTemplates].map( + id => + new NestedFilter({ + id: id, + label: this._appStateService.getDossierTemplateById(id).name + }) + ); this.filterService.addFilterGroup({ slug: 'dossierTemplateFilters', @@ -276,10 +299,13 @@ export class DossierListingScreenComponent checker: (dw: Dossier) => quickFilters.reduce((acc, f) => acc || (f.checked && f.checker(dw)), false) }); - const dossierFilters = this.entitiesService.all.map(dossier => ({ - key: dossier.dossierName, - label: dossier.dossierName - })); + const dossierFilters = this.entitiesService.all.map( + dossier => + new NestedFilter({ + id: dossier.dossierName, + label: dossier.dossierName + }) + ); this.filterService.addFilterGroup({ slug: 'dossierNameFilter', label: this._translateService.instant('dossier-listing.filters.label'), @@ -290,30 +316,30 @@ export class DossierListingScreenComponent }); } - private _createQuickFilters() { + private _createQuickFilters(): INestedFilter[] { const myDossiersLabel = this._translateService.instant('dossier-listing.quick-filters.my-dossiers'); - const filters: NestedFilter[] = [ + const filters: INestedFilter[] = [ { - key: 'my-dossiers', + id: 'my-dossiers', label: myDossiersLabel, checker: (dw: Dossier) => dw.ownerId === this.currentUser.id }, { - key: 'to-approve', + id: 'to-approve', label: this._translateService.instant('dossier-listing.quick-filters.to-approve'), checker: (dw: Dossier) => dw.approverIds.includes(this.currentUser.id) }, { - key: 'to-review', + id: 'to-review', label: this._translateService.instant('dossier-listing.quick-filters.to-review'), checker: (dw: Dossier) => dw.memberIds.includes(this.currentUser.id) }, { - key: 'other', + id: 'other', label: this._translateService.instant('dossier-listing.quick-filters.other'), checker: (dw: Dossier) => !dw.memberIds.includes(this.currentUser.id) } - ]; + ].map(filter => new NestedFilter(filter)); return filters.filter(f => f.label === myDossiersLabel || this._userPreferenceService.areDevFeaturesEnabled); } diff --git a/apps/red-ui/src/app/modules/dossier/screens/dossier-overview-screen/dossier-overview-screen.component.ts b/apps/red-ui/src/app/modules/dossier/screens/dossier-overview-screen/dossier-overview-screen.component.ts index aae6642aa..984770673 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/dossier-overview-screen/dossier-overview-screen.component.ts +++ b/apps/red-ui/src/app/modules/dossier/screens/dossier-overview-screen/dossier-overview-screen.component.ts @@ -33,6 +33,7 @@ import { ActionConfig } from '@shared/components/page-header/models/action-confi import { CircleButtonTypes, DefaultListingServices, + INestedFilter, keyChecker, ListingComponent, ListingModes, @@ -45,7 +46,6 @@ import { } from '@iqser/common-ui'; import { DossierAttributesService } from '@shared/services/controller-wrappers/dossier-attributes.service'; import { DossierAttributeWithValue } from '@models/dossier-attributes.model'; -import { UserPreferenceService } from '@services/user-preference.service'; import { workloadTranslations } from '../../translations/workload-translations'; import { fileStatusTranslations } from '../../translations/file-status-translations'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; @@ -80,15 +80,14 @@ export class DossierOverviewScreenComponent extends ListingComponent; - @ViewChild('addedOnTemplate', { static: true }) addedOnTemplate: TemplateRef; - @ViewChild('attributeTemplate', { static: true }) attributeTemplate: TemplateRef; - @ViewChild('needsWorkTemplate', { static: true }) needsWorkTemplate: TemplateRef; - @ViewChild('reviewerTemplate', { static: true }) reviewerTemplate: TemplateRef; - @ViewChild('pagesTemplate', { static: true }) pagesTemplate: TemplateRef; - @ViewChild('statusTemplate', { static: true }) statusTemplate: TemplateRef; + @ViewChild('filenameTemplate', { static: true }) filenameTemplate: TemplateRef; + @ViewChild('addedOnTemplate', { static: true }) addedOnTemplate: TemplateRef; + @ViewChild('attributeTemplate', { static: true }) attributeTemplate: TemplateRef; + @ViewChild('needsWorkTemplate', { static: true }) needsWorkTemplate: TemplateRef; + @ViewChild('reviewerTemplate', { static: true }) reviewerTemplate: TemplateRef; + @ViewChild('pagesTemplate', { static: true }) pagesTemplate: TemplateRef; + @ViewChild('statusTemplate', { static: true }) statusTemplate: TemplateRef; readonly workflowConfig: WorkflowConfig; - protected readonly _primaryKey = 'filename'; @ViewChild(DossierDetailsComponent, { static: false }) private readonly _dossierDetailsComponent: DossierDetailsComponent; private _lastScrolledIndex: number; @@ -112,7 +111,6 @@ export class DossierOverviewScreenComponent extends ListingComponent (this._lastScrolledIndex = index))) .subscribe(); - this.searchService.setSearchKey('filename'); - this.dossierAttributes = await this._dossierAttributesService.getValues(this.currentDossier); } catch (e) { } finally { @@ -296,22 +292,22 @@ export class DossierOverviewScreenComponent extends ListingComponent { await this._uploadFiles(convertFiles(files, this.currentDossier)); this._fileInput.nativeElement.value = null; } - async bulkActionPerformed() { + async bulkActionPerformed(): Promise { this.entitiesService.setSelected([]); await this.reloadDossiers(); } @@ -351,7 +347,7 @@ export class DossierOverviewScreenComponent extends ListingComponent(item => ({ - key: item, - label: this._translateService.instant(fileStatusTranslations[item]) - })); + const statusFilters = [...allDistinctFileStatusWrapper].map( + item => + new NestedFilter({ + id: item, + label: this._translateService.instant(fileStatusTranslations[item]) + }) + ); this.filterService.addFilterGroup({ slug: 'statusFilters', label: this._translateService.instant('filters.status'), icon: 'red:status', - filters: statusFilters.sort(StatusSorter.byStatus), + filters: statusFilters.sort((a, b) => StatusSorter[a.id] - StatusSorter[b.id]), checker: keyChecker('status') }); - const peopleFilters = []; + const peopleFilters: INestedFilter[] = []; if (allDistinctPeople.has(undefined) || allDistinctPeople.has(null)) { allDistinctPeople.delete(undefined); allDistinctPeople.delete(null); - peopleFilters.push({ - key: null, - label: this._translateService.instant('initials-avatar.unassigned') - }); + peopleFilters.push( + new NestedFilter({ + id: null, + label: this._translateService.instant('initials-avatar.unassigned') + }) + ); } allDistinctPeople.forEach(userId => { - peopleFilters.push({ - key: userId, - label: this._userService.getNameForId(userId) - }); + peopleFilters.push( + new NestedFilter({ + id: userId, + label: this._userService.getNameForId(userId) + }) + ); }); this.filterService.addFilterGroup({ slug: 'peopleFilters', @@ -498,10 +501,13 @@ export class DossierOverviewScreenComponent extends ListingComponent(item => ({ - key: item, - label: workloadTranslations[item] - })); + const needsWorkFilters = [...allDistinctNeedsWork].map( + item => + new NestedFilter({ + id: item, + label: workloadTranslations[item] + }) + ); this.filterService.addFilterGroup({ slug: 'needsWorkFilters', @@ -520,11 +526,14 @@ export class DossierOverviewScreenComponent extends ListingComponent((value: string) => ({ - key: value, - label: value === '-' ? this._translateService.instant('filters.empty') : value - })), - checker: (input: FileStatusWrapper, filter: NestedFilter) => filter.key === input.fileAttributes.attributeIdToValue[id] + filters: [...filterValue].map( + (value: string) => + new NestedFilter({ + id: value, + label: value === '-' ? this._translateService.instant('filters.empty') : value + }) + ), + checker: (input: FileStatusWrapper, filter: INestedFilter) => filter.id === input.fileAttributes.attributeIdToValue[id] }); }); @@ -537,10 +546,13 @@ export class DossierOverviewScreenComponent extends ListingComponent acc || f.checker(file), false)) }); - const filesNamesFilters = this.entitiesService.all.map(file => ({ - key: file.filename, - label: file.filename - })); + const filesNamesFilters = this.entitiesService.all.map( + file => + new NestedFilter({ + id: file.filename, + label: file.filename + }) + ); this.filterService.addFilterGroup({ slug: 'filesNamesFilter', @@ -552,13 +564,13 @@ export class DossierOverviewScreenComponent extends ListingComponent 0) { const recentPeriod = this._configService.values.RECENT_PERIOD_IN_HOURS; quickFilters = [ { - key: 'recent', + id: 'recent', label: this._translateService.instant('dossier-overview.quick-filters.recent', { hours: recentPeriod }), @@ -571,20 +583,20 @@ export class DossierOverviewScreenComponent extends ListingComponent file.currentReviewer === this.currentUser.id }, { - key: 'unassigned', + id: 'unassigned', label: this._translateService.instant('dossier-overview.quick-filters.unassigned'), checker: (file: FileStatusWrapper) => !file.currentReviewer }, { - key: 'assigned-to-others', + id: 'assigned-to-others', label: this._translateService.instant('dossier-overview.quick-filters.assigned-to-others'), checker: (file: FileStatusWrapper) => !!file.currentReviewer && file.currentReviewer !== this.currentUser.id } - ]; + ].map(filter => new NestedFilter(filter)); } } diff --git a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/file-preview-screen.component.ts b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/file-preview-screen.component.ts index 6debca254..4a2191a29 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/file-preview-screen.component.ts +++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/file-preview-screen.component.ts @@ -8,8 +8,8 @@ import { CircleButtonTypes, Debounce, FilterService, + INestedFilter, LoadingService, - NestedFilter, processFilters, Toaster } from '@iqser/common-ui'; @@ -80,7 +80,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni read: TemplateRef, static: true }) - private readonly _filterTemplate: TemplateRef; + private readonly _filterTemplate: TemplateRef; @ViewChild('fileActions') fileActions: FileActionsComponent; constructor( 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 703f67968..04f3fc4c4 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 @@ -1,23 +1,29 @@ import { Component, forwardRef, Injector, OnDestroy, OnInit, TemplateRef, ViewChild } from '@angular/core'; -import { DefaultListingServices, IListable, keyChecker, ListingComponent, LoadingService, TableColumnConfig } from '@iqser/common-ui'; -import { MatchedDocument, SearchControllerService, SearchResult } from '@redaction/red-ui-http'; +import { + DefaultListingServices, + IListable, + keyChecker, + ListingComponent, + LoadingService, + NestedFilter, + TableColumnConfig +} from '@iqser/common-ui'; +import { List, MatchedDocument, SearchControllerService, SearchResult } from '@redaction/red-ui-http'; import { BehaviorSubject, Observable } from 'rxjs'; import { debounceTime, map, skip, switchMap, tap } from 'rxjs/operators'; import { ActivatedRoute, Router } from '@angular/router'; import { AppStateService } from '@state/app-state.service'; -import { FileStatusWrapper } from '@models/file/file-status.wrapper'; 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 { Dossier } from '../../../../state/model/dossier'; import { TranslateService } from '@ngx-translate/core'; import { RouterHistoryService } from '@services/router-history.service'; interface ListItem extends IListable { readonly dossierId: string; readonly filename: string; - readonly unmatched: readonly string[] | null; - readonly highlights: Record; + readonly unmatched: List | null; + readonly highlights: Record; readonly routerLink: string; readonly status: string; readonly dossierName: string; @@ -26,7 +32,7 @@ interface ListItem extends IListable { interface SearchInput { readonly query: string; - readonly dossierIds?: readonly string[]; + readonly dossierIds?: List; } @Component({ @@ -53,7 +59,6 @@ export class SearchScreenComponent extends ListingComponent implements tap(result => this.entitiesService.setEntities(result)), tap(() => this._loadingService.stop()) ); - protected readonly _primaryKey = 'filename'; constructor( private readonly _router: Router, @@ -72,10 +77,13 @@ export class SearchScreenComponent extends ListingComponent implements label: this._translateService.instant('search-screen.filters.by-dossier'), filterceptionPlaceholder: this._translateService.instant('search-screen.filters.search-placeholder'), icon: 'red:folder', - filters: this._appStateService.allDossiers.map(dossier => ({ - key: dossier.id, - label: dossier.dossierName - })), + filters: this._appStateService.allDossiers.map( + dossier => + new NestedFilter({ + id: dossier.id, + label: dossier.dossierName + }) + ), checker: keyChecker('id') }); @@ -89,17 +97,13 @@ export class SearchScreenComponent extends ListingComponent implements this.addSubscription = this.searchService.valueChanges$.pipe(debounceTime(300)).subscribe(value => this.updateNavigation(value)); this.addSubscription = this.filterService.filterGroups$.pipe(skip(1)).subscribe(group => { - const dossierIds = group[0].filters.filter(v => v.checked).map(v => v.key); + const dossierIds = group[0].filters.filter(v => v.checked).map(v => v.id); this.search$.next({ query: this.searchService.searchValue, dossierIds: dossierIds }); }); } routerLinkFn = (entity: ListItem) => [entity.routerLink]; - setInitialConfig(): void { - return; - } - updateNavigation(query: string, mustContain?: string): void { const newQuery = query?.replace(mustContain, `"${mustContain}"`); const queryParams = newQuery && newQuery !== '' ? { query: newQuery } : {}; @@ -137,14 +141,6 @@ export class SearchScreenComponent extends ListingComponent implements this.search$.next({ query, dossierIds: dossierId ? [dossierId] : [] }); } - private _getFileWrapper(dossierId: string, fileId: string): FileStatusWrapper { - return this._appStateService.getFileById(dossierId, fileId); - } - - private _getDossierWrapper(dossierId: string): Dossier { - return this._appStateService.getDossierById(dossierId); - } - private _toMatchedDocuments({ matchedDocuments }: SearchResult): MatchedDocument[] { return matchedDocuments.filter(doc => doc.score > 0 && doc.matchedTerms.length > 0); } @@ -154,7 +150,7 @@ export class SearchScreenComponent extends ListingComponent implements } private _toListItem({ dossierId, fileId, unmatchedTerms, highlights }: MatchedDocument): ListItem { - const fileWrapper = this._getFileWrapper(dossierId, fileId); + const fileWrapper = this._appStateService.getFileById(dossierId, fileId); if (!fileWrapper) { return undefined; } @@ -166,8 +162,9 @@ export class SearchScreenComponent extends ListingComponent implements highlights, status: fileWrapper.status, numberOfPages: fileWrapper.numberOfPages, - dossierName: this._getDossierWrapper(dossierId).dossierName, + dossierName: this._appStateService.getDossierById(dossierId).dossierName, filename: fileWrapper.filename, + searchKey: fileWrapper.filename, routerLink: `/main/dossiers/${dossierId}/file/${fileId}` }; } diff --git a/apps/red-ui/src/app/modules/dossier/services/annotation-processing.service.ts b/apps/red-ui/src/app/modules/dossier/services/annotation-processing.service.ts index e426b0bf7..17f9830db 100644 --- a/apps/red-ui/src/app/modules/dossier/services/annotation-processing.service.ts +++ b/apps/red-ui/src/app/modules/dossier/services/annotation-processing.service.ts @@ -1,38 +1,36 @@ import { Injectable } from '@angular/core'; import { AnnotationWrapper } from '@models/file/annotation.wrapper'; import { SuperTypeSorter } from '@utils/sorters/super-type-sorter'; -import { handleCheckedValue, NestedFilter } from '@iqser/common-ui'; +import { handleCheckedValue, IFilter, INestedFilter, NestedFilter } from '@iqser/common-ui'; import { annotationTypesTranslations } from '../../../translations/annotation-types-translations'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; @Injectable() export class AnnotationProcessingService { - static get secondaryAnnotationFilters(): NestedFilter[] { + static get secondaryAnnotationFilters(): INestedFilter[] { return [ { - key: 'with-comments', + id: 'with-comments', icon: 'red:comment', label: _('filter-menu.with-comments'), checked: false, topLevelFilter: true, - children: [], checker: (annotation: AnnotationWrapper) => annotation?.comments?.length > 0 }, { - key: 'with-reason-changes', + id: 'with-reason-changes', icon: 'red:reason', label: _('filter-menu.with-reason-changes'), checked: false, topLevelFilter: true, - children: [], checker: (annotation: AnnotationWrapper) => annotation?.legalBasisChangeValue?.length > 0 } - ]; + ].map(item => new NestedFilter(item)); } - getAnnotationFilter(annotations: AnnotationWrapper[]): NestedFilter[] { - const filterMap = new Map(); - const filters: NestedFilter[] = []; + getAnnotationFilter(annotations: AnnotationWrapper[]): INestedFilter[] { + const filterMap = new Map(); + const filters: INestedFilter[] = []; annotations?.forEach(a => { const topLevelFilter = a.superType !== 'hint' && a.superType !== 'redaction' && a.superType !== 'recommendation'; @@ -49,11 +47,11 @@ export class AnnotationProcessingService { if (!parentFilter) { parentFilter = this._createParentFilter(a.superType, filterMap, filters); } - const childFilter = { - key: a.type, + const childFilter: IFilter = { + id: a.type, label: a.type, + searchKey: a.type, checked: false, - filters: [], matches: 1 }; filterMap.set(key, childFilter); @@ -63,7 +61,7 @@ export class AnnotationProcessingService { }); for (const filter of filters) { - filter.children.sort((a, b) => a.key.localeCompare(b.key)); + filter.children.sort((a, b) => a.id.localeCompare(b.id)); handleCheckedValue(filter); if (filter.checked || filter.indeterminate) { filter.expanded = true; @@ -73,13 +71,13 @@ export class AnnotationProcessingService { } } - return filters.sort((a, b) => SuperTypeSorter[a.key] - SuperTypeSorter[b.key]); + return filters.sort((a, b) => SuperTypeSorter[a.id] - SuperTypeSorter[b.id]); } filterAndGroupAnnotations( annotations: AnnotationWrapper[], - primaryFilters: NestedFilter[], - secondaryFilters?: NestedFilter[] + primaryFilters: INestedFilter[], + secondaryFilters?: INestedFilter[] ): Map { const obj = new Map(); @@ -116,21 +114,20 @@ export class AnnotationProcessingService { return obj; } - private _createParentFilter(key: string, filterMap: Map, filters: NestedFilter[]) { - const filter: NestedFilter = { - key: key, + private _createParentFilter(key: string, filterMap: Map, filters: INestedFilter[]) { + const filter: INestedFilter = new NestedFilter({ + id: key, topLevelFilter: true, matches: 1, - label: annotationTypesTranslations[key], - children: [] - }; + label: annotationTypesTranslations[key] + }); filterMap.set(key, filter); filters.push(filter); return filter; } - private _getFlatFilters(filters: NestedFilter[], filterBy?: (f: NestedFilter) => boolean) { - const flatFilters: NestedFilter[] = []; + private _getFlatFilters(filters: INestedFilter[], filterBy?: (f: INestedFilter) => boolean) { + const flatFilters: INestedFilter[] = []; filters.forEach(filter => { flatFilters.push(filter); @@ -140,7 +137,7 @@ export class AnnotationProcessingService { return filterBy ? flatFilters.filter(f => filterBy(f)) : flatFilters; } - private _matchesOne = (filters: NestedFilter[], condition: (filter: NestedFilter) => boolean): boolean => { + private _matchesOne = (filters: INestedFilter[], condition: (filter: INestedFilter) => boolean): boolean => { if (filters.length === 0) { return true; } @@ -154,7 +151,7 @@ export class AnnotationProcessingService { return false; }; - private _matchesAll = (filters: NestedFilter[], condition: (filter: NestedFilter) => boolean): boolean => { + private _matchesAll = (filters: INestedFilter[], condition: (filter: INestedFilter) => boolean): boolean => { if (filters.length === 0) { return true; } @@ -168,11 +165,11 @@ export class AnnotationProcessingService { return true; }; - private _checkByFilterKey = (filter: NestedFilter, annotation: AnnotationWrapper) => { + private _checkByFilterKey = (filter: INestedFilter, annotation: AnnotationWrapper) => { const superType = annotation.superType; const isNotTopLevelFilter = superType === 'hint' || superType === 'redaction' || superType === 'recommendation'; - return filter.key === superType || (filter.key === annotation.type && isNotTopLevelFilter); + return filter.id === superType || (filter.id === annotation.type && isNotTopLevelFilter); }; private _sortAnnotations(annotations: AnnotationWrapper[]): AnnotationWrapper[] { 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 e572822c4..2fc02853e 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 @@ -1,7 +1,7 @@ import { Component, EventEmitter, Input, Optional, Output, TemplateRef } from '@angular/core'; import { ActionConfig } from '@shared/components/page-header/models/action-config.model'; import { ButtonConfig } from '@shared/components/page-header/models/button-config.model'; -import { FilterService, IconButtonTypes, Listable, SearchService } from '@iqser/common-ui'; +import { FilterService, IListable, SearchService } from '@iqser/common-ui'; import { distinctUntilChanged, map } from 'rxjs/operators'; import { combineLatest, Observable, of } from 'rxjs'; import { SearchPosition, SearchPositions } from '@shared/components/page-header/models/search-positions.type'; @@ -12,7 +12,7 @@ import { FileAttributeConfig } from '@redaction/red-ui-http'; templateUrl: './page-header.component.html', styleUrls: ['./page-header.component.scss'] }) -export class PageHeaderComponent { +export class PageHeaderComponent { readonly searchPositions = SearchPositions; readonly iconButtonTypes = IconButtonTypes; diff --git a/apps/red-ui/src/app/modules/shared/components/simple-doughnut-chart/simple-doughnut-chart.component.ts b/apps/red-ui/src/app/modules/shared/components/simple-doughnut-chart/simple-doughnut-chart.component.ts index 07271d371..f5f8cf4af 100644 --- a/apps/red-ui/src/app/modules/shared/components/simple-doughnut-chart/simple-doughnut-chart.component.ts +++ b/apps/red-ui/src/app/modules/shared/components/simple-doughnut-chart/simple-doughnut-chart.component.ts @@ -56,7 +56,7 @@ export class SimpleDoughnutChartComponent implements OnChanges { } filterChecked$(key: string): Observable { - return this.statusFilters$.pipe(map(all => all?.find(e => e.key === key)?.checked)); + return this.statusFilters$.pipe(map(all => all?.find(e => e.id === key)?.checked)); } calculateChartData() { diff --git a/apps/red-ui/src/app/utils/filter-utils.ts b/apps/red-ui/src/app/utils/filter-utils.ts index 00af3590e..cb5be195d 100644 --- a/apps/red-ui/src/app/utils/filter-utils.ts +++ b/apps/red-ui/src/app/utils/filter-utils.ts @@ -1,38 +1,38 @@ import { FileStatusWrapper } from '@models/file/file-status.wrapper'; import { Dossier } from '../state/model/dossier'; -import { handleCheckedValue, NestedFilter } from '@iqser/common-ui'; +import { handleCheckedValue, INestedFilter } from '@iqser/common-ui'; -export function handleFilterDelta(oldFilters: NestedFilter[], newFilters: NestedFilter[], allFilters: NestedFilter[]) { +export function handleFilterDelta(oldFilters: INestedFilter[], newFilters: INestedFilter[], allFilters: INestedFilter[]) { const newFiltersDelta = {}; for (const newFilter of newFilters) { - const oldFilter = oldFilters.find(f => f.key === newFilter.key); + const oldFilter = oldFilters.find(f => f.id === newFilter.id); if (!oldFilter || oldFilter.matches !== newFilter.matches) { - newFiltersDelta[newFilter.key] = {}; - newFilter.children.forEach(filter => (newFiltersDelta[newFilter.key][filter.key] = {})); + newFiltersDelta[newFilter.id] = {}; + newFilter.children.forEach(filter => (newFiltersDelta[newFilter.id][filter.id] = {})); } if (!oldFilter) { for (const childFilter of newFilter.children) { - const oldFilterChild = oldFilter?.children.find(f => f.key === childFilter.key); + const oldFilterChild = oldFilter?.children.find(f => f.id === childFilter.id); if (!oldFilterChild || oldFilterChild.matches !== childFilter.matches) { - if (!newFiltersDelta[newFilter.key]) { - newFiltersDelta[newFilter.key] = {}; + if (!newFiltersDelta[newFilter.id]) { + newFiltersDelta[newFilter.id] = {}; } - newFiltersDelta[newFilter.key][childFilter.key] = {}; + newFiltersDelta[newFilter.id][childFilter.id] = {}; } } } } for (const key of Object.keys(newFiltersDelta)) { - const foundFilter = allFilters.find(f => f.key === key); + const foundFilter = allFilters.find(f => f.id === key); if (foundFilter) { // if has children if (!foundFilter.children?.length) { foundFilter.checked = true; } else { for (const subKey of Object.keys(newFiltersDelta[key])) { - const childFilter = foundFilter.children.find(f => f.key === subKey); + const childFilter = foundFilter.children.find(f => f.id === subKey); if (childFilter) { childFilter.checked = true; } @@ -45,8 +45,8 @@ export function handleFilterDelta(oldFilters: NestedFilter[], newFilters: Nested }); } -export const annotationFilterChecker = (input: FileStatusWrapper | Dossier, filter: NestedFilter) => { - switch (filter.key) { +export const annotationFilterChecker = (input: FileStatusWrapper | Dossier, filter: INestedFilter) => { + switch (filter.id) { case 'analysis': { if (input instanceof Dossier) { return input.reanalysisRequired; @@ -78,10 +78,10 @@ export const annotationFilterChecker = (input: FileStatusWrapper | Dossier, filt } }; -export const dossierStatusChecker = (dw: Dossier, filter: NestedFilter) => dw.hasStatus(filter.key); +export const dossierStatusChecker = (dw: Dossier, filter: INestedFilter) => dw.hasStatus(filter.id); -export const dossierMemberChecker = (dw: Dossier, filter: NestedFilter) => dw.hasMember(filter.key); +export const dossierMemberChecker = (dw: Dossier, filter: INestedFilter) => dw.hasMember(filter.id); -export const dossierTemplateChecker = (dw: Dossier, filter: NestedFilter) => dw.dossierTemplateId === filter.key; +export const dossierTemplateChecker = (dw: Dossier, filter: INestedFilter) => dw.dossierTemplateId === filter.id; -export const dossierApproverChecker = (dw: Dossier, filter: NestedFilter) => dw.approverIds.includes(filter.key); +export const dossierApproverChecker = (dw: Dossier, filter: INestedFilter) => dw.approverIds.includes(filter.id); diff --git a/apps/red-ui/src/app/utils/sorters/redaction-filter-sorter.ts b/apps/red-ui/src/app/utils/sorters/redaction-filter-sorter.ts index 82e055c3b..90d07be73 100644 --- a/apps/red-ui/src/app/utils/sorters/redaction-filter-sorter.ts +++ b/apps/red-ui/src/app/utils/sorters/redaction-filter-sorter.ts @@ -6,5 +6,5 @@ export const RedactionFilterSorter = { hint: 4, suggestion: 5, none: 6, - byKey: (a: { key: string }, b: { key: string }) => RedactionFilterSorter[a.key] - RedactionFilterSorter[b.key] + byKey: (a: { id: string }, b: { id: string }) => RedactionFilterSorter[a.id] - RedactionFilterSorter[b.id] };