From 220a2c6be919b2138417d50a4269e9034976645d Mon Sep 17 00:00:00 2001 From: Dan Percic Date: Tue, 29 Jun 2021 23:34:40 +0300 Subject: [PATCH 01/20] add padding to user dropdown actions --- .../assign-user-dropdown/assign-user-dropdown.component.html | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/red-ui/src/app/modules/shared/components/assign-user-dropdown/assign-user-dropdown.component.html b/apps/red-ui/src/app/modules/shared/components/assign-user-dropdown/assign-user-dropdown.component.html index 958acff92..ebd73b821 100644 --- a/apps/red-ui/src/app/modules/shared/components/assign-user-dropdown/assign-user-dropdown.component.html +++ b/apps/red-ui/src/app/modules/shared/components/assign-user-dropdown/assign-user-dropdown.component.html @@ -11,6 +11,7 @@ Date: Tue, 29 Jun 2021 23:55:40 +0300 Subject: [PATCH 02/20] use querylist to get popup filters --- .../dossier-template-actions.component.html | 6 +- .../dossier-listing-screen.component.html | 4 - .../dossier-listing-screen.component.ts | 35 ++++---- .../dossier-overview-screen.component.html | 4 - .../dossier-overview-screen.component.ts | 84 +++++++------------ .../shared/base/base-listing.component.ts | 8 +- 6 files changed, 53 insertions(+), 88 deletions(-) diff --git a/apps/red-ui/src/app/modules/admin/components/dossier-template-actions/dossier-template-actions.component.html b/apps/red-ui/src/app/modules/admin/components/dossier-template-actions/dossier-template-actions.component.html index b8cc0482b..964d802d3 100644 --- a/apps/red-ui/src/app/modules/admin/components/dossier-template-actions/dossier-template-actions.component.html +++ b/apps/red-ui/src/app/modules/admin/components/dossier-template-actions/dossier-template-actions.component.html @@ -5,8 +5,7 @@ icon="red:trash" tooltip="dossier-templates-listing.action.delete" type="dark-bg" - > - + > - + > diff --git a/apps/red-ui/src/app/modules/dossier/screens/dossier-listing-screen/dossier-listing-screen.component.html b/apps/red-ui/src/app/modules/dossier/screens/dossier-listing-screen/dossier-listing-screen.component.html index 0fe731f87..756c7e49c 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/dossier-listing-screen/dossier-listing-screen.component.html +++ b/apps/red-ui/src/app/modules/dossier/screens/dossier-listing-screen/dossier-listing-screen.component.html @@ -3,21 +3,18 @@
; + @ViewChild(QuickFiltersComponent) + protected readonly _quickFiltersComponent: QuickFiltersComponent; + constructor( readonly permissionsService: PermissionsService, private readonly _translateChartService: TranslateChartService, @@ -95,16 +105,6 @@ export class DossierListingScreenComponent return this.allEntities.length - this.activeDossiersCount; } - protected get _filterComponents(): (PopupFilterComponent | QuickFiltersComponent)[] { - return [ - this._statusFilterComponent, - this._peopleFilterComponent, - this._needsWorkFilterComponent, - this._dossierTemplateFilterComponent, - this._quickFiltersComponent - ]; - } - protected get _filters(): { values: FilterModel[]; checker: Function; @@ -156,6 +156,7 @@ export class DossierListingScreenComponent this._lastScrollPosition = this.scrollViewport.measureScrollOffset('top'); } }); + this._filterComponents = [...this._filterList.toArray(), this._quickFiltersComponent]; } ngOnAttach() { diff --git a/apps/red-ui/src/app/modules/dossier/screens/dossier-overview-screen/dossier-overview-screen.component.html b/apps/red-ui/src/app/modules/dossier/screens/dossier-overview-screen/dossier-overview-screen.component.html index 895a5dcf6..d18157645 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/dossier-overview-screen/dossier-overview-screen.component.html +++ b/apps/red-ui/src/app/modules/dossier/screens/dossier-overview-screen/dossier-overview-screen.component.html @@ -3,21 +3,18 @@
; + @ViewChild(QuickFiltersComponent) + protected readonly _quickFiltersComponent: QuickFiltersComponent; + @ViewChild('fileInput') private _fileInput: ElementRef; constructor( @@ -110,15 +118,6 @@ export class DossierOverviewScreenComponent return this.quickFilters.filter(f => !f.required && f.checked); } - protected get _filterComponents(): (PopupFilterComponent | QuickFiltersComponent)[] { - return [ - this._statusFilterComponent, - this._peopleFilterComponent, - this._needsWorkFilterComponent, - this._quickFiltersComponent - ]; - } - protected get _filters(): { values: FilterModel[]; checker: Function; @@ -184,6 +183,8 @@ export class DossierOverviewScreenComponent this._lastScrollPosition = this.scrollViewport.measureScrollOffset('top'); } }); + + this._filterComponents = [...this._filterList.toArray(), this._quickFiltersComponent]; } ngOnDestroy(): void { @@ -223,10 +224,6 @@ export class DossierOverviewScreenComponent }); } - isPending(fileStatusWrapper: FileStatusWrapper) { - return fileStatusWrapper.status === FileStatus.StatusEnum.UNPROCESSED; - } - isError(fileStatusWrapper: FileStatusWrapper) { return fileStatusWrapper.status === FileStatus.StatusEnum.ERROR; } @@ -278,12 +275,7 @@ export class DossierOverviewScreenComponent fileLink(fileStatus: FileStatusWrapper) { return this.permissionsService.canOpenFile(fileStatus) - ? [ - '/main/dossiers/' + - this.activeDossier.dossier.dossierId + - '/file/' + - fileStatus.fileId - ] + ? [`/main/dossiers/${this.activeDossier.dossierId}/file/${fileStatus.fileId}`] : []; } @@ -347,28 +339,18 @@ export class DossierOverviewScreenComponent } private _computeAllFilters() { - if (!this.activeDossier) { - return; - } + if (!this.activeDossier) return; const allDistinctFileStatusWrapper = new Set(); const allDistinctPeople = new Set(); const allDistinctAddedDates = new Set(); const allDistinctNeedsWork = new Set(); - // All people - this.allEntities.forEach(file => allDistinctPeople.add(file.currentReviewer)); - - // File statuses - this.allEntities.forEach(file => allDistinctFileStatusWrapper.add(file.status)); - - // Added dates - this.allEntities.forEach(file => - allDistinctAddedDates.add(moment(file.added).format('DD/MM/YYYY')) - ); - - // Needs work this.allEntities.forEach(file => { + allDistinctPeople.add(file.currentReviewer); + allDistinctFileStatusWrapper.add(file.status); + allDistinctAddedDates.add(moment(file.added).format('DD/MM/YYYY')); + if (this.permissionsService.fileRequiresReanalysis(file)) allDistinctNeedsWork.add('analysis'); if (file.hintsOnly) allDistinctNeedsWork.add('hint'); @@ -379,13 +361,10 @@ export class DossierOverviewScreenComponent if (file.hasNone) allDistinctNeedsWork.add('none'); }); - const statusFilters = []; - allDistinctFileStatusWrapper.forEach(status => { - statusFilters.push({ - key: status, - label: this._translateService.instant(status) - }); - }); + const statusFilters = [...allDistinctFileStatusWrapper].map(item => ({ + key: item, + label: this._translateService.instant(item) + })); statusFilters.sort((a, b) => StatusSorter[a.key] - StatusSorter[b.key]); this.statusFilters = processFilters(this.statusFilters, statusFilters); @@ -407,13 +386,10 @@ export class DossierOverviewScreenComponent }); this.peopleFilters = processFilters(this.peopleFilters, peopleFilters); - const needsWorkFilters = []; - allDistinctNeedsWork.forEach(type => { - needsWorkFilters.push({ - key: type, - label: `filter.${type}` - }); - }); + const needsWorkFilters = [...allDistinctNeedsWork].map(item => ({ + key: item, + label: this._translateService.instant('filter.' + item) + })); needsWorkFilters.sort( (a, b) => RedactionFilterSorter[a.key] - RedactionFilterSorter[b.key] ); diff --git a/apps/red-ui/src/app/modules/shared/base/base-listing.component.ts b/apps/red-ui/src/app/modules/shared/base/base-listing.component.ts index 01be98b5c..96db88b82 100644 --- a/apps/red-ui/src/app/modules/shared/base/base-listing.component.ts +++ b/apps/red-ui/src/app/modules/shared/base/base-listing.component.ts @@ -30,6 +30,8 @@ export abstract class BaseListingComponent { protected readonly _searchKey: string; protected readonly _selectionKey: string; protected readonly _sortKey: ScreenName; + // Overwrite this in ngOnInit + protected _filterComponents: (PopupFilterComponent | QuickFiltersComponent)[] = []; protected constructor(protected readonly _injector: Injector) { this._formBuilder = this._injector.get(FormBuilder); @@ -41,7 +43,7 @@ export abstract class BaseListingComponent { get hasActiveFilters() { return ( this._filterComponents - .filter(f => !!f) + ?.filter(f => !!f) .reduce((prev, component) => prev || component?.hasActiveFilters, false) || this.searchForm.get('query').value ); @@ -71,10 +73,6 @@ export abstract class BaseListingComponent { return []; } - protected get _filterComponents(): (PopupFilterComponent | QuickFiltersComponent)[] { - return []; - } - // ---- private get _getSearchKey(): string { From 9011427c06e0964bdc6aefa7d8a655a07e11b62c Mon Sep 17 00:00:00 2001 From: Dan Percic Date: Wed, 30 Jun 2021 00:54:46 +0300 Subject: [PATCH 03/20] created trash screen with placeholders --- .../base-screen/base-screen.component.html | 34 ++-- .../base-screen/base-screen.component.ts | 46 ++++- .../app/modules/admin/admin-routing.module.ts | 9 + .../src/app/modules/admin/admin.module.ts | 2 + .../screens/trash/trash-screen.component.html | 187 ++++++++++++++++++ .../screens/trash/trash-screen.component.scss | 44 +++++ .../screens/trash/trash-screen.component.ts | 42 ++++ 7 files changed, 339 insertions(+), 25 deletions(-) create mode 100644 apps/red-ui/src/app/modules/admin/screens/trash/trash-screen.component.html create mode 100644 apps/red-ui/src/app/modules/admin/screens/trash/trash-screen.component.scss create mode 100644 apps/red-ui/src/app/modules/admin/screens/trash/trash-screen.component.ts diff --git a/apps/red-ui/src/app/components/base-screen/base-screen.component.html b/apps/red-ui/src/app/components/base-screen/base-screen.component.html index 367536e93..166e42a9a 100644 --- a/apps/red-ui/src/app/components/base-screen/base-screen.component.html +++ b/apps/red-ui/src/app/components/base-screen/base-screen.component.html @@ -94,30 +94,26 @@ *ngIf="userPreferenceService.areDevFeaturesEnabled" class="mr-8" > + - - - - + + + + + +
diff --git a/apps/red-ui/src/assets/i18n/en.json b/apps/red-ui/src/assets/i18n/en.json index fb30e0b9d..25b455450 100644 --- a/apps/red-ui/src/assets/i18n/en.json +++ b/apps/red-ui/src/assets/i18n/en.json @@ -1271,6 +1271,34 @@ }, "title": "Configure SMTP Account" }, + "trash": { + "label": "Trash", + "table-header": { + "title": "{{length}} deleted dossiers", + "info": "Deleted items can be restored up to {{hours}} hours from their deletions" + }, + "bulk": { + "delete": "Forever Delete Selected Dossiers", + "restore": "Restore Selected Dossiers" + }, + "action": { + "delete": "Delete forever", + "restore": "Restore" + }, + "search": "Search...", + "table-col-names": { + "name": "Name", + "owner": "Owner", + "deleted-on": "Deleted on", + "time-to-restore": "Time to restore" + }, + "no-data": { + "title": "There are no dossiers yet." + }, + "no-match": { + "title": "No dossiers match your current filters." + } + }, "sorting": { "alphabetically": "Alphabetically", "custom": "Custom", @@ -1289,13 +1317,9 @@ "children": { "admin": "Settings", "downloads": "My Downloads", - "language": { - "de": "German", - "en": "English", - "label": "Language" - }, - "logout": "Logout", - "my-profile": "My Profile" + "my-profile": "My Profile", + "trash": "Trash", + "logout": "Logout" } } } From 53d8406c23be66ecf959e52f947d9fb8997172d5 Mon Sep 17 00:00:00 2001 From: Dan Percic Date: Wed, 7 Jul 2021 15:33:37 +0300 Subject: [PATCH 07/20] add actions to page header, reset quick filters from page header --- .../dossier-listing-screen.component.html | 8 +- .../dossier-listing-screen.component.ts | 33 +++-- .../dossier-overview-screen.component.html | 114 +++++------------- .../dossier-overview-screen.component.scss | 4 - .../dossier-overview-screen.component.ts | 48 ++++---- .../shared/base/base-listing.component.ts | 22 ++-- .../icon-button/icon-button.component.ts | 9 +- .../popup-filter/popup-filter.component.ts | 19 ++- .../page-header/models/action-config.model.ts | 5 + .../page-header/models/base-config.model.ts | 5 + .../page-header/models/button-config.model.ts | 7 ++ .../page-header/models/filter-config.model.ts | 8 ++ .../page-header/page-header.component.html | 43 ++++--- .../page-header/page-header.component.scss | 4 +- .../page-header/page-header.component.ts | 64 ++++------ 15 files changed, 174 insertions(+), 219 deletions(-) create mode 100644 apps/red-ui/src/app/modules/shared/components/page-header/models/action-config.model.ts create mode 100644 apps/red-ui/src/app/modules/shared/components/page-header/models/base-config.model.ts create mode 100644 apps/red-ui/src/app/modules/shared/components/page-header/models/button-config.model.ts create mode 100644 apps/red-ui/src/app/modules/shared/components/page-header/models/filter-config.model.ts diff --git a/apps/red-ui/src/app/modules/dossier/screens/dossier-listing-screen/dossier-listing-screen.component.html b/apps/red-ui/src/app/modules/dossier/screens/dossier-listing-screen/dossier-listing-screen.component.html index 1dcc66ff5..a45e9638e 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/dossier-listing-screen/dossier-listing-screen.component.html +++ b/apps/red-ui/src/app/modules/dossier/screens/dossier-listing-screen/dossier-listing-screen.component.html @@ -1,10 +1,12 @@
@@ -20,7 +22,7 @@
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 1a62be189..c9789db01 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 @@ -24,9 +24,9 @@ import { dossierTemplateChecker, processFilters } from '@shared/components/filters/popup-filter/utils/filter-utils'; -import { QuickFiltersComponent } from '@shared/components/filters/quick-filters/quick-filters.component'; import { UserPreferenceService } from '../../../../services/user-preference.service'; -import { ButtonConfig, FilterConfig } from '@shared/components/page-header/page-header.component'; +import { FilterConfig } from '../../../shared/components/page-header/models/filter-config.model'; +import { ButtonConfig } from '../../../shared/components/page-header/models/button-config.model'; @Component({ templateUrl: './dossier-listing-screen.component.html', @@ -48,11 +48,10 @@ export class DossierListingScreenComponent statusFilters: [] }; quickFilters: FilterModel[]; - readonly itemSize = 85; filterConfigs: FilterConfig[]; buttonConfigs: ButtonConfig[] = [ { - label: 'dossier-listing.add-new', + label: this._translateService.instant('dossier-listing.add-new'), action: () => this.openAddDossierDialog(), hide: !this.permissionsService.isManager(), icon: 'red:plus', @@ -60,6 +59,8 @@ export class DossierListingScreenComponent } ]; + readonly itemSize = 85; + protected readonly _searchKey = 'name'; protected readonly _sortKey = 'dossier-listing'; @@ -68,8 +69,6 @@ export class DossierListingScreenComponent private _routerEventsScrollPositionSub: Subscription; private _fileChangedSub: Subscription; - @ViewChild(QuickFiltersComponent) - protected readonly _quickFiltersComponent: QuickFiltersComponent; @ViewChild('needsWorkTemplate', { read: TemplateRef, static: true }) private readonly _needsWorkTemplate: TemplateRef; @@ -156,7 +155,6 @@ export class DossierListingScreenComponent this._lastScrollPosition = this.scrollViewport.measureScrollOffset('top'); } }); - this._filterComponents = [this._quickFiltersComponent]; } ngOnAttach() { @@ -317,23 +315,23 @@ export class DossierListingScreenComponent private _createFilterConfigs() { this.filterConfigs = [ { - label: 'filters.status', + label: this._translateService.instant('filters.status'), primaryFilters: this.statusFilters, icon: 'red:status' }, { - label: 'filters.people', + label: this._translateService.instant('filters.people'), primaryFilters: this.peopleFilters, icon: 'red:user' }, { - label: 'filters.needs-work', + label: this._translateService.instant('filters.needs-work'), primaryFilters: this.needsWorkFilters, icon: 'red:needs-work', filterTemplate: this._needsWorkTemplate }, { - label: 'filters.dossier-templates', + label: this._translateService.instant('filters.dossier-templates'), primaryFilters: this.dossierTemplateFilters, icon: 'red:template', hide: this.dossierTemplateFilters.length <= 1 @@ -342,32 +340,33 @@ export class DossierListingScreenComponent } private _createQuickFilters() { - const filters = [ + const filters: FilterModel[] = [ { key: this.user.id, - label: 'dossier-listing.quick-filters.my-dossiers', + label: this._translateService.instant('dossier-listing.quick-filters.my-dossiers'), checker: (dw: DossierWrapper) => dw.ownerId === this.user.id }, { key: this.user.id, - label: 'dossier-listing.quick-filters.to-approve', + label: this._translateService.instant('dossier-listing.quick-filters.to-approve'), checker: (dw: DossierWrapper) => dw.approverIds.includes(this.user.id) }, { key: this.user.id, - label: 'dossier-listing.quick-filters.to-review', + label: this._translateService.instant('dossier-listing.quick-filters.to-review'), checker: (dw: DossierWrapper) => dw.memberIds.includes(this.user.id) }, { key: this.user.id, - label: 'dossier-listing.quick-filters.other', + label: this._translateService.instant('dossier-listing.quick-filters.other'), checker: (dw: DossierWrapper) => !dw.memberIds.includes(this.user.id) } ]; this.quickFilters = filters.filter( f => - f.label === 'dossier-listing.quick-filters.my-dossiers' || + f.label === + this._translateService.instant('dossier-listing.quick-filters.my-dossiers') || this._userPreferenceService.areDevFeaturesEnabled ); } diff --git a/apps/red-ui/src/app/modules/dossier/screens/dossier-overview-screen/dossier-overview-screen.component.html b/apps/red-ui/src/app/modules/dossier/screens/dossier-overview-screen/dossier-overview-screen.component.html index a53154b42..a0cd15405 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/dossier-overview-screen/dossier-overview-screen.component.html +++ b/apps/red-ui/src/app/modules/dossier/screens/dossier-overview-screen/dossier-overview-screen.component.html @@ -1,91 +1,41 @@
- - - - - - - - - - - - - - - - - - - - - - + [showResetFilters]="showResetFilters" + [showCloseButton]="true" + [searchPlaceholder]="'dossier-overview.search' | translate" + > + - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +
@@ -113,7 +63,7 @@ >
diff --git a/apps/red-ui/src/app/modules/dossier/screens/dossier-overview-screen/dossier-overview-screen.component.scss b/apps/red-ui/src/app/modules/dossier/screens/dossier-overview-screen/dossier-overview-screen.component.scss index a0f607c1e..a84f71a23 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/dossier-overview-screen/dossier-overview-screen.component.scss +++ b/apps/red-ui/src/app/modules/dossier/screens/dossier-overview-screen/dossier-overview-screen.component.scss @@ -96,10 +96,6 @@ cdk-virtual-scroll-viewport { } } -.ml-6 { - margin-left: 6px; -} - .mr-4 { margin-right: 4px; } 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 d7de6c947..8eba4f95a 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 @@ -37,12 +37,9 @@ import { processFilters } from '@shared/components/filters/popup-filter/utils/filter-utils'; import { FilterModel } from '@shared/components/filters/popup-filter/model/filter.model'; -import { QuickFiltersComponent } from '../../../shared/components/filters/quick-filters/quick-filters.component'; -import { AppConfigService } from '../../../app-config/app-config.service'; -import { - ActionConfig, - FilterConfig -} from '../../../shared/components/page-header/page-header.component'; +import { AppConfigKey, AppConfigService } from '../../../app-config/app-config.service'; +import { FilterConfig } from '@shared/components/page-header/models/filter-config.model'; +import { ActionConfig } from '@shared/components/page-header/models/action-config.model'; @Component({ selector: 'redaction-dossier-overview-screen', @@ -78,8 +75,6 @@ export class DossierOverviewScreenComponent private _lastScrollPosition: number; private _lastOpenedFileId = ''; - @ViewChild(QuickFiltersComponent) - protected readonly _quickFiltersComponent: QuickFiltersComponent; @ViewChild('needsWorkTemplate', { read: TemplateRef, static: true }) private readonly _needsWorkTemplate: TemplateRef; @ViewChild('fileInput') private _fileInput: ElementRef; @@ -184,8 +179,6 @@ export class DossierOverviewScreenComponent this._lastScrollPosition = this.scrollViewport.measureScrollOffset('top'); } }); - - this._filterComponents = [this._quickFiltersComponent]; } ngOnDestroy(): void { @@ -317,7 +310,7 @@ export class DossierOverviewScreenComponent recentlyModifiedChecker = (file: FileStatusWrapper) => moment(file.lastUpdated) - .add(this._appConfigService.getConfig('RECENT_PERIOD_IN_HOURS'), 'hours') + .add(this._appConfigService.getConfig(AppConfigKey.RECENT_PERIOD_IN_HOURS), 'hours') .isAfter(moment()); protected _preFilter() { @@ -403,12 +396,15 @@ export class DossierOverviewScreenComponent private _createQuickFilters() { if (this.allEntities.filter(this.recentlyModifiedChecker).length > 0) { - const recentPeriodInHours = this._appConfigService.getConfig('RECENT_PERIOD_IN_HOURS'); + const recentPeriod = this._appConfigService.getConfig( + AppConfigKey.RECENT_PERIOD_IN_HOURS + ); this.quickFilters = [ { key: this.user.id, - label: 'dossier-overview.quick-filters.recent', - labelParams: { hours: recentPeriodInHours }, + label: this._translateService.instant('dossier-overview.quick-filters.recent', { + hours: recentPeriod + }), required: true, checker: this.recentlyModifiedChecker } @@ -421,17 +417,21 @@ export class DossierOverviewScreenComponent ...this.quickFilters, { key: this.user.id, - label: 'dossier-overview.quick-filters.assigned-to-me', + label: this._translateService.instant( + 'dossier-overview.quick-filters.assigned-to-me' + ), checker: (file: FileStatusWrapper) => file.currentReviewer === this.user.id }, { key: this.user.id, - label: 'dossier-overview.quick-filters.unassigned', + label: this._translateService.instant('dossier-overview.quick-filters.unassigned'), checker: (file: FileStatusWrapper) => !file.currentReviewer }, { key: this.user.id, - label: 'dossier-overview.quick-filters.assigned-to-others', + label: this._translateService.instant( + 'dossier-overview.quick-filters.assigned-to-others' + ), checker: (file: FileStatusWrapper) => !!file.currentReviewer && file.currentReviewer !== this.user.id } @@ -441,17 +441,17 @@ export class DossierOverviewScreenComponent private _createFilterConfigs() { this.filterConfigs = [ { - label: 'filters.status', + label: this._translateService.instant('filters.status'), primaryFilters: this.statusFilters, icon: 'red:status' }, { - label: 'filters.assigned-people', + label: this._translateService.instant('filters.assigned-people'), primaryFilters: this.peopleFilters, icon: 'red:user' }, { - label: 'filters.needs-work', + label: this._translateService.instant('filters.needs-work'), primaryFilters: this.needsWorkFilters, icon: 'red:needs-work', filterTemplate: this._needsWorkTemplate @@ -462,13 +462,7 @@ export class DossierOverviewScreenComponent private _createActionConfigs() { this.actionConfigs = [ { - label: 'dossier-overview.header-actions.edit', - action: $event => this.openEditDossierDialog($event), - icon: 'red:edit', - hide: !this.permissionsService.isManager() - }, - { - label: 'dossier-overview.header-actions.edit', + label: this._translateService.instant('dossier-overview.header-actions.edit'), action: $event => this.openEditDossierDialog($event), icon: 'red:edit', hide: !this.permissionsService.isManager() diff --git a/apps/red-ui/src/app/modules/shared/base/base-listing.component.ts b/apps/red-ui/src/app/modules/shared/base/base-listing.component.ts index 9099c9837..d2a1c65a3 100644 --- a/apps/red-ui/src/app/modules/shared/base/base-listing.component.ts +++ b/apps/red-ui/src/app/modules/shared/base/base-listing.component.ts @@ -3,7 +3,6 @@ import { FormBuilder, FormGroup } from '@angular/forms'; import { debounce } from '@utils/debounce'; import { ScreenName, SortingOption, SortingService } from '@services/sorting.service'; import { FilterModel } from '../components/filters/popup-filter/model/filter.model'; -import { PopupFilterComponent } from '../components/filters/popup-filter/popup-filter.component'; import { getFilteredEntities } from '../components/filters/popup-filter/utils/filter-utils'; import { QuickFiltersComponent } from '../components/filters/quick-filters/quick-filters.component'; import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling'; @@ -19,6 +18,7 @@ export abstract class BaseListingComponent { displayedEntities: T[] = []; selectedEntitiesIds: string[] = []; searchForm: FormGroup; + showResetFilters = false; @ViewChild(CdkVirtualScrollViewport) scrollViewport: CdkVirtualScrollViewport; protected readonly _formBuilder: FormBuilder; @@ -31,7 +31,8 @@ export abstract class BaseListingComponent { protected readonly _selectionKey: string; protected readonly _sortKey: ScreenName; // Overwrite this in ngOnInit - protected _filterComponents: (PopupFilterComponent | QuickFiltersComponent)[] = []; + @ViewChild(QuickFiltersComponent) + protected _quickFilters: QuickFiltersComponent; private _searchValue = ''; @@ -87,26 +88,25 @@ export abstract class BaseListingComponent { return this._sortKey; } - filtersChanged(filters?: { [key: string]: FilterModel[] }): void { - if (filters) { - for (const key of Object.keys(filters)) { - for (let idx = 0; idx < this[key].length; ++idx) { + filtersChanged(filters?: { [key: string]: FilterModel[] } | FilterModel[]): void { + if (filters instanceof Array) this.showResetFilters = !!filters.find(f => f.checked); + else + for (const key of Object.keys(filters ?? {})) { + for (let idx = 0; idx < this[key]?.length; ++idx) { this[key][idx] = filters[key][idx]; } } - } + this._filterEntities(); } resetFilters() { - for (const filterComponent of this._filterComponents.filter(f => !!f)) { - filterComponent.deactivateFilters(); - } + this._quickFilters.deactivateFilters(); + this.showResetFilters = false; this.filtersChanged(); } // Filter - toggleEntitySelected($event: MouseEvent, entity: T) { $event.stopPropagation(); const idx = this.selectedEntitiesIds.indexOf(entity[this._getSelectionKey]); diff --git a/apps/red-ui/src/app/modules/shared/components/buttons/icon-button/icon-button.component.ts b/apps/red-ui/src/app/modules/shared/components/buttons/icon-button/icon-button.component.ts index 507d9f601..0faab82ab 100644 --- a/apps/red-ui/src/app/modules/shared/components/buttons/icon-button/icon-button.component.ts +++ b/apps/red-ui/src/app/modules/shared/components/buttons/icon-button/icon-button.component.ts @@ -1,5 +1,12 @@ import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; +export type IconButtonType = 'default' | 'show-bg' | 'primary'; +export enum IconButtonTypes { + DEFAULT = 'default', + SHOW_BG = 'show-bg', + PRIMARY = 'primary' +} + @Component({ selector: 'redaction-icon-button', templateUrl: './icon-button.component.html', @@ -11,6 +18,6 @@ export class IconButtonComponent { @Input() text: string; @Input() showDot = false; @Input() disabled = false; - @Input() type: 'default' | 'show-bg' | 'primary' = 'default'; + @Input() type: IconButtonType = IconButtonTypes.DEFAULT; @Output() action = new EventEmitter(); } diff --git a/apps/red-ui/src/app/modules/shared/components/filters/popup-filter/popup-filter.component.ts b/apps/red-ui/src/app/modules/shared/components/filters/popup-filter/popup-filter.component.ts index d63f4486d..0064a4f40 100644 --- a/apps/red-ui/src/app/modules/shared/components/filters/popup-filter/popup-filter.component.ts +++ b/apps/red-ui/src/app/modules/shared/components/filters/popup-filter/popup-filter.component.ts @@ -11,6 +11,7 @@ import { import { FilterModel } from './model/filter.model'; import { handleCheckedValue } from './utils/filter-utils'; import { MAT_CHECKBOX_DEFAULT_OPTIONS } from '@angular/material/checkbox'; +import { TranslateService } from '@ngx-translate/core'; @Component({ selector: 'redaction-popup-filter', @@ -36,22 +37,20 @@ export class PopupFilterComponent implements OnChanges { @Input() actionsTemplate: TemplateRef; @Input() primaryFilters: FilterModel[] = []; @Input() secondaryFilters: FilterModel[] = []; - @Input() filterLabel = 'filter-menu.label'; + @Input() filterLabel = this._translateService.instant('filter-menu.label'); @Input() icon: string; @Input() chevron = false; atLeastOneFilterIsExpandable = false; atLeastOneSecondaryFilterIsExpandable = false; - constructor(private readonly _changeDetectorRef: ChangeDetectorRef) {} + constructor( + private readonly _changeDetectorRef: ChangeDetectorRef, + private readonly _translateService: TranslateService + ) {} get hasActiveFilters(): boolean { - for (const filter of this._allFilters) { - if (filter.checked || filter.indeterminate) { - return true; - } - } - return false; + return !!this._allFilters.find(f => f.checked || f.indeterminate); } private get _allFilters(): FilterModel[] { @@ -90,11 +89,11 @@ export class PopupFilterComponent implements OnChanges { } applyFilters() { - this._changeDetectorRef.detectChanges(); this.filtersChanged.emit({ primary: this.primaryFilters, secondary: this.secondaryFilters }); + this._changeDetectorRef.detectChanges(); } toggleFilterExpanded($event: MouseEvent, filter: FilterModel) { @@ -103,7 +102,7 @@ export class PopupFilterComponent implements OnChanges { } isExpandable(filter: FilterModel) { - return filter.filters && filter.filters.length > 0; + return filter?.filters?.length > 0; } _(obj): FilterModel { diff --git a/apps/red-ui/src/app/modules/shared/components/page-header/models/action-config.model.ts b/apps/red-ui/src/app/modules/shared/components/page-header/models/action-config.model.ts new file mode 100644 index 000000000..223953f8f --- /dev/null +++ b/apps/red-ui/src/app/modules/shared/components/page-header/models/action-config.model.ts @@ -0,0 +1,5 @@ +import { BaseHeaderConfig } from './base-config.model'; + +export interface ActionConfig extends BaseHeaderConfig { + action: ($event) => void; +} diff --git a/apps/red-ui/src/app/modules/shared/components/page-header/models/base-config.model.ts b/apps/red-ui/src/app/modules/shared/components/page-header/models/base-config.model.ts new file mode 100644 index 000000000..238f47d39 --- /dev/null +++ b/apps/red-ui/src/app/modules/shared/components/page-header/models/base-config.model.ts @@ -0,0 +1,5 @@ +export interface BaseHeaderConfig { + label: string; + icon?: string; + hide?: boolean; +} diff --git a/apps/red-ui/src/app/modules/shared/components/page-header/models/button-config.model.ts b/apps/red-ui/src/app/modules/shared/components/page-header/models/button-config.model.ts new file mode 100644 index 000000000..d9dca889e --- /dev/null +++ b/apps/red-ui/src/app/modules/shared/components/page-header/models/button-config.model.ts @@ -0,0 +1,7 @@ +import { IconButtonType } from '../../buttons/icon-button/icon-button.component'; +import { BaseHeaderConfig } from './base-config.model'; + +export interface ButtonConfig extends BaseHeaderConfig { + action: ($event) => void; + type?: IconButtonType; +} diff --git a/apps/red-ui/src/app/modules/shared/components/page-header/models/filter-config.model.ts b/apps/red-ui/src/app/modules/shared/components/page-header/models/filter-config.model.ts new file mode 100644 index 000000000..29b749703 --- /dev/null +++ b/apps/red-ui/src/app/modules/shared/components/page-header/models/filter-config.model.ts @@ -0,0 +1,8 @@ +import { FilterModel } from '../../filters/popup-filter/model/filter.model'; +import { TemplateRef } from '@angular/core'; +import { BaseHeaderConfig } from './base-config.model'; + +export interface FilterConfig extends BaseHeaderConfig { + primaryFilters: FilterModel[]; + filterTemplate?: TemplateRef; +} 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 99d20c336..c953293a9 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,5 +1,5 @@ -
- - - -
- - +
+ + + + + + + +
diff --git a/apps/red-ui/src/app/modules/shared/components/page-header/page-header.component.scss b/apps/red-ui/src/app/modules/shared/components/page-header/page-header.component.scss index 434b95fc1..acbdfdf11 100644 --- a/apps/red-ui/src/app/modules/shared/components/page-header/page-header.component.scss +++ b/apps/red-ui/src/app/modules/shared/components/page-header/page-header.component.scss @@ -1,3 +1,3 @@ -.page-header .actions > *:not(:last-child) { - margin-right: 16px; +.ml-6 { + margin-left: 6px; } 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 465871018..922ca72ce 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,33 +5,16 @@ import { Input, Output, QueryList, - TemplateRef, ViewChildren } from '@angular/core'; import { PermissionsService } from '@services/permissions.service'; import { FilterModel } from '@shared/components/filters/popup-filter/model/filter.model'; import { PopupFilterComponent } from '@shared/components/filters/popup-filter/popup-filter.component'; -import { FormBuilder, FormGroup } from '@angular/forms'; - -interface BaseHeaderConfig { - label: string; - icon?: string; - hide?: boolean; -} - -export interface FilterConfig extends BaseHeaderConfig { - primaryFilters: FilterModel[]; - filterTemplate?: TemplateRef; -} - -export interface ActionConfig extends BaseHeaderConfig { - action: ($event) => void; -} - -export interface ButtonConfig extends BaseHeaderConfig { - action: ($event) => void; - type?: 'default' | 'show-bg' | 'primary'; -} +import { FormBuilder } from '@angular/forms'; +import { FilterConfig } from '@shared/components/page-header/models/filter-config.model'; +import { ActionConfig } from '@shared/components/page-header/models/action-config.model'; +import { ButtonConfig } from '@shared/components/page-header/models/button-config.model'; +import { BaseHeaderConfig } from '@shared/components/page-header/models/base-config.model'; @Component({ selector: 'redaction-page-header', @@ -42,14 +25,21 @@ export interface ButtonConfig extends BaseHeaderConfig { export class PageHeaderComponent { @Input() pageLabel: string; @Input() showCloseButton: boolean; + @Input() showResetFilters: boolean; @Input() filterConfigs: FilterConfig[]; @Input() actionConfigs: ActionConfig[]; @Input() buttonConfigs: ButtonConfig[]; @Input() searchPlaceholder: string; - @Output() filtersChanged = new EventEmitter(); + @Output() filtersChanged = new EventEmitter<{ + primary: FilterModel[]; + secondary?: FilterModel[]; + }>(); + @Output() filtersReset = new EventEmitter(); @Output() searchChanged = new EventEmitter(); - searchForm: FormGroup; + readonly searchForm = this._formBuilder.group({ + query: [''] + }); @ViewChildren(PopupFilterComponent) private readonly _filterComponents: QueryList; @@ -58,34 +48,24 @@ export class PageHeaderComponent { readonly permissionsService: PermissionsService, private readonly _formBuilder: FormBuilder ) { - this._initSearch(); + this.searchForm.valueChanges.subscribe(value => this.searchChanged.emit(value.query)); } get hasActiveFilters() { - return ( - this._filterComponents - ?.filter(f => !!f) - .reduce((prev, component) => prev || component?.hasActiveFilters, false) || - this.searchForm.get('query').value + const hasActiveFilters = this._filterComponents?.reduce( + (acc, component) => acc || component?.hasActiveFilters, + false ); + return hasActiveFilters || this.searchForm.get('query').value || this.showResetFilters; } resetFilters() { - for (const filterComponent of this._filterComponents.filter(f => !!f)) { - filterComponent.deactivateFilters(); - } - this.filtersChanged.emit(); + this._filterComponents.forEach(component => component?.deactivateFilters()); + + this.filtersReset.emit(); this.searchForm.reset({ query: '' }); } - private _initSearch() { - this.searchForm = this._formBuilder.group({ - query: [''] - }); - - this.searchForm.valueChanges.subscribe(value => this.searchChanged.emit(value.query)); - } - trackByLabel(index: number, item: BaseHeaderConfig) { return item.label; } From 7990636066e474d2ee4f4efbaa7fb22b849eada9 Mon Sep 17 00:00:00 2001 From: Dan Percic Date: Wed, 7 Jul 2021 20:07:47 +0300 Subject: [PATCH 08/20] show time to restore --- .../screens/trash/trash-screen.component.html | 86 +++++++++---------- .../screens/trash/trash-screen.component.ts | 60 ++++++++++--- .../src/app/modules/shared/pipes/date.pipe.ts | 62 +++++++++++++ .../src/app/modules/shared/shared.module.ts | 2 + apps/red-ui/src/assets/i18n/en.json | 8 ++ libs/red-ui-http/src/lib/model/dossier.ts | 2 + 6 files changed, 160 insertions(+), 60 deletions(-) create mode 100644 apps/red-ui/src/app/modules/shared/pipes/date.pipe.ts diff --git a/apps/red-ui/src/app/modules/admin/screens/trash/trash-screen.component.html b/apps/red-ui/src/app/modules/admin/screens/trash/trash-screen.component.html index 92a0e7c2e..e8f0dd7dc 100644 --- a/apps/red-ui/src/app/modules/admin/screens/trash/trash-screen.component.html +++ b/apps/red-ui/src/app/modules/admin/screens/trash/trash-screen.component.html @@ -1,7 +1,10 @@
- +
@@ -20,22 +23,20 @@ }} - - - + -
- -
+ + + + + + +
@@ -88,82 +89,75 @@
-
+
- {{ dw.dossierName }} + {{ entity.dossierName }}
- {{ getDossierTemplate(dw).name }} + {{ getDossierTemplate(entity.dossierTemplateId).name }}
-
- - {{ dw.files.length }} -
-
- - {{ dw.totalNumberOfPages }} -
- {{ dw.numberOfMembers }} + {{ entity.memberIds.length }}
- {{ dw.dossier.date | date: 'mediumDate' }} + {{ entity.date | date: 'mediumDate' }}
-
+
- {{ dw.dossier.dueDate | date: 'mediumDate' }} + {{ entity.dueDate | date: 'mediumDate' }}
- {{ dw.dossierDate | date: 'd MMM. yyyy' }} + {{ entity.softDeletedTime | date: 'd MMM. yyyy' }}
- {{ dw.dossier.date | date: 'd MMM. yyyy' }} + {{ getRestoreDate(entity.softDeletedTime) | date: 'timeFromNow' }}
diff --git a/apps/red-ui/src/app/modules/admin/screens/trash/trash-screen.component.ts b/apps/red-ui/src/app/modules/admin/screens/trash/trash-screen.component.ts index 5ef38b988..bbd15896b 100644 --- a/apps/red-ui/src/app/modules/admin/screens/trash/trash-screen.component.ts +++ b/apps/red-ui/src/app/modules/admin/screens/trash/trash-screen.component.ts @@ -1,42 +1,74 @@ import { Component, Injector, OnInit } from '@angular/core'; import { BaseListingComponent } from '@shared/base/base-listing.component'; -import { AdminDialogService } from '../../services/admin-dialog.service'; import { AppStateService } from '@state/app-state.service'; import { PermissionsService } from '@services/permissions.service'; -import { UserPreferenceService } from '@services/user-preference.service'; -import { DossierWrapper } from '@state/model/dossier.wrapper'; -import { DossierTemplateModel } from '@redaction/red-ui-http'; +import { Dossier, DossierControllerService, DossierTemplateModel } from '@redaction/red-ui-http'; +import { LoadingService } from '../../../../services/loading.service'; +import { AppConfigKey, AppConfigService } from '../../../app-config/app-config.service'; +import * as moment from 'moment'; +import { TranslateService } from '@ngx-translate/core'; @Component({ templateUrl: './trash-screen.component.html', styleUrls: ['./trash-screen.component.scss'] }) -export class TrashScreenComponent extends BaseListingComponent implements OnInit { +export class TrashScreenComponent extends BaseListingComponent implements OnInit { readonly itemSize = 85; + private readonly _deleteRetentionHours = this._appConfigService.getConfig( + AppConfigKey.DELETE_RETENTION_HOURS + ); - protected readonly _searchKey = 'name'; + protected readonly _searchKey = 'dossierName'; protected readonly _selectionKey = 'dossierId'; protected readonly _sortKey = 'dossier-listing'; constructor( - private readonly _dialogService: AdminDialogService, private readonly _appStateService: AppStateService, readonly permissionsService: PermissionsService, - protected readonly _injector: Injector + protected readonly _injector: Injector, + private readonly _dossierControllerService: DossierControllerService, + private readonly _loadingService: LoadingService, + private readonly _appConfigService: AppConfigService, + private readonly _translateService: TranslateService ) { super(_injector); } - ngOnInit(): void { - this.loadDossierTemplatesData(); + async ngOnInit(): Promise { + this._loadingService.start(); + await this.loadDossierTemplatesData(); + this._loadingService.stop(); } - loadDossierTemplatesData() { - this.allEntities = this._appStateService.allDossiers; + async loadDossierTemplatesData(): Promise { + this.allEntities = await this._dossierControllerService.getDeletedDossiers().toPromise(); + console.log(this.allEntities); this._executeSearchImmediately(); } - getDossierTemplate(dw: DossierWrapper): DossierTemplateModel { - return this._appStateService.getDossierTemplateById(dw.dossier.dossierTemplateId); + getDossierTemplate(dossierTemplateId: string): DossierTemplateModel { + return this._appStateService.getDossierTemplateById(dossierTemplateId); + } + + getRestoreDate(softDeletedTime: string) { + return moment(softDeletedTime).add(5, 'hours').format(); + } + + async restore(dossier: Dossier) { + this._loadingService.start(); + await this._dossierControllerService.restoreDossiers([dossier.dossierId]).toPromise(); + this.allEntities = this.allEntities.filter(e => e !== dossier); + this._loadingService.stop(); + } + + async hardDelete(dossier: Dossier) { + this._loadingService.start(); + await this._dossierControllerService.hardDeleteDossiers([dossier.dossierId]).toPromise(); + this.allEntities = this.allEntities.filter(e => e !== dossier); + this._loadingService.stop(); + } + + trackById(index: number, dossier: Dossier) { + return dossier.dossierId; } } diff --git a/apps/red-ui/src/app/modules/shared/pipes/date.pipe.ts b/apps/red-ui/src/app/modules/shared/pipes/date.pipe.ts new file mode 100644 index 000000000..4de9e5b87 --- /dev/null +++ b/apps/red-ui/src/app/modules/shared/pipes/date.pipe.ts @@ -0,0 +1,62 @@ +import { Inject, LOCALE_ID, Pipe, PipeTransform } from '@angular/core'; +import * as moment from 'moment'; +import { TranslateService } from '@ngx-translate/core'; +import { DatePipe as BaseDatePipe } from '@angular/common'; + +const HOURS_IN_A_DAY = 24; +const MINUTES_IN_AN_HOUR = 60; + +@Pipe({ + name: 'date' +}) +export class DatePipe extends BaseDatePipe implements PipeTransform { + constructor( + @Inject(LOCALE_ID) private readonly _locale: string, + private readonly _translateService: TranslateService + ) { + super(_locale); + } + + transform(value: null | undefined, format?: string, timezone?: string, locale?: string): null; + transform( + value: Date | string | number | null | undefined, + format?: string, + timezone?: string, + locale?: string + ): string | null; + transform(value: any, format?: string, timezone?: string, locale?: string): string { + if (format === 'timeFromNow') return this._getTimeFromNow(value); + return super.transform(value, format, timezone, locale); + } + + private _getTimeFromNow(item: string) { + const date = moment(item); + const now = new Date(Date.now()); + + const daysLeft = date.diff(now, 'days'); + const hoursFromNow = date.diff(now, 'hours'); + const hoursLeft = hoursFromNow - HOURS_IN_A_DAY * daysLeft; + const minutesFromNow = date.diff(now, 'minutes'); + const minutesLeft = minutesFromNow - HOURS_IN_A_DAY * MINUTES_IN_AN_HOUR * daysLeft; + + if (daysLeft === 0 && hoursLeft === 0 && minutesLeft > 0) + return this._translate('time.less-than-an-hour'); + + const hoursSuffix = this._translate(`time.hour${hoursLeft === 1 ? '' : 's'}`); + const hoursDisplay = `${hoursLeft} ${hoursSuffix}`; + + if (daysLeft === 0 && hoursLeft > 0) return hoursDisplay; + + const daysSuffix = this._translate(`time.day${daysLeft === 1 ? '' : 's'}`); + const daysDisplay = `${daysLeft} ${daysSuffix}`; + + if (daysLeft > 0 && hoursLeft > 0) return `${daysDisplay} ${hoursDisplay}`; + if (daysLeft > 0) return daysDisplay; + + return this._translate(`time.no-time-left`); + } + + private _translate(value: string, params?: { [key: string]: string }) { + return this._translateService.instant(value, params); + } +} diff --git a/apps/red-ui/src/app/modules/shared/shared.module.ts b/apps/red-ui/src/app/modules/shared/shared.module.ts index 4118cd4da..eca2e5034 100644 --- a/apps/red-ui/src/app/modules/shared/shared.module.ts +++ b/apps/red-ui/src/app/modules/shared/shared.module.ts @@ -38,6 +38,7 @@ import { PopupFilterComponent } from '@shared/components/filters/popup-filter/po import { AssignUserDropdownComponent } from './components/assign-user-dropdown/assign-user-dropdown.component'; import { InputWithActionComponent } from '@shared/components/input-with-action/input-with-action.component'; import { PageHeaderComponent } from './components/page-header/page-header.component'; +import { DatePipe } from '@shared/pipes/date.pipe'; const buttons = [ ChevronButtonComponent, @@ -75,6 +76,7 @@ const components = [ const utils = [ HumanizePipe, + DatePipe, SyncWidthDirective, HasScrollbarDirective, NavigateLastDossiersScreenDirective diff --git a/apps/red-ui/src/assets/i18n/en.json b/apps/red-ui/src/assets/i18n/en.json index 25b455450..cfd4dc125 100644 --- a/apps/red-ui/src/assets/i18n/en.json +++ b/apps/red-ui/src/assets/i18n/en.json @@ -1404,5 +1404,13 @@ "text-placeholder": "Enter text" }, "title": "Watermark" + }, + "time": { + "no-time-left": "Time to restore already passed", + "less-than-an-hour": "< 1 hour", + "hour": "hour", + "hours": "hours", + "day": "day", + "days": "days" } } diff --git a/libs/red-ui-http/src/lib/model/dossier.ts b/libs/red-ui-http/src/lib/model/dossier.ts index f950fa242..66668b2e5 100644 --- a/libs/red-ui-http/src/lib/model/dossier.ts +++ b/libs/red-ui-http/src/lib/model/dossier.ts @@ -19,10 +19,12 @@ export interface Dossier { dossierTemplateId?: string; downloadFileTypes?: Array; dueDate?: string; + hardDeletedTime?: string; memberIds?: Array; ownerId?: string; reportTemplateIds?: Array; reportTypes?: Array; + softDeletedTime?: string; status?: Dossier.StatusEnum; watermarkEnabled?: boolean; } From 3e400e6dd2f0b6cdace9a6e38847f78655302b07 Mon Sep 17 00:00:00 2001 From: Dan Percic Date: Thu, 8 Jul 2021 01:59:09 +0300 Subject: [PATCH 09/20] split listing component into services --- .../dossier-listing-screen.component.html | 15 +-- .../dossier-listing-screen.component.ts | 114 +++++++++++------- .../dossier-overview-screen.component.html | 4 - .../dossier-overview-screen.component.ts | 4 +- .../shared/base/base-listing.component.ts | 13 +- .../shared/base/new-base-listing.component.ts | 69 +++++++++++ .../model/filter-wrapper.model.ts | 8 ++ .../popup-filter/utils/filter-utils.ts | 14 +-- .../page-header/page-header.component.html | 5 +- .../page-header/page-header.component.ts | 45 +++---- .../modules/shared/services/filter.service.ts | 47 ++++++++ .../shared/services/screen-state.service.ts | 103 ++++++++++++++++ .../modules/shared/services/search.service.ts | 60 +++++++++ 13 files changed, 398 insertions(+), 103 deletions(-) create mode 100644 apps/red-ui/src/app/modules/shared/base/new-base-listing.component.ts create mode 100644 apps/red-ui/src/app/modules/shared/components/filters/popup-filter/model/filter-wrapper.model.ts create mode 100644 apps/red-ui/src/app/modules/shared/services/filter.service.ts create mode 100644 apps/red-ui/src/app/modules/shared/services/screen-state.service.ts create mode 100644 apps/red-ui/src/app/modules/shared/services/search.service.ts diff --git a/apps/red-ui/src/app/modules/dossier/screens/dossier-listing-screen/dossier-listing-screen.component.html b/apps/red-ui/src/app/modules/dossier/screens/dossier-listing-screen/dossier-listing-screen.component.html index a45e9638e..89ce05585 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/dossier-listing-screen/dossier-listing-screen.component.html +++ b/apps/red-ui/src/app/modules/dossier/screens/dossier-listing-screen/dossier-listing-screen.component.html @@ -1,11 +1,7 @@
@@ -17,7 +13,7 @@ {{ 'dossier-listing.table-header.title' - | translate: { length: displayedEntities.length || 0 } + | translate: { length: (displayed$ | async)?.length || 0 } }} @@ -54,14 +50,14 @@ @@ -69,7 +65,8 @@
+ extends NewBaseListingComponent implements OnInit, OnDestroy, OnAttach, OnDetach { dossiersChartData: DoughnutChartConfig[] = []; @@ -61,7 +65,6 @@ export class DossierListingScreenComponent readonly itemSize = 85; - protected readonly _searchKey = 'name'; protected readonly _sortKey = 'dossier-listing'; private _dossierAutoUpdateTimer: Subscription; @@ -85,23 +88,26 @@ export class DossierListingScreenComponent ) { super(_injector); this._appStateService.reset(); + this._searchService.searchKey = 'name'; this._loadEntitiesFromState(); } - get noData() { - return this.allEntities.length === 0; + get activeDossiersCount(): number { + return this._screenStateService.entities.filter( + p => p.dossier.status === Dossier.StatusEnum.ACTIVE + ).length; } - get user() { - return this._userService.user; + get inactiveDossiersCount(): number { + return this._screenStateService.entities.length - this.activeDossiersCount; } - get activeDossiersCount() { - return this.allEntities.filter(p => p.dossier.status === Dossier.StatusEnum.ACTIVE).length; + get displayed$(): Observable { + return this._screenStateService.displayedEntities$; } - get inactiveDossiersCount() { - return this.allEntities.length - this.activeDossiersCount; + get entities$(): Observable { + return this._screenStateService.entities$; } protected get _filters(): { @@ -129,32 +135,37 @@ export class DossierListingScreenComponent } ngOnInit(): void { - this._calculateData(); - - this._dossierAutoUpdateTimer = timer(0, 10000) - .pipe( - tap(async () => { - await this._appStateService.loadAllDossiers(); - this._loadEntitiesFromState(); - }) - ) - .subscribe(); - - this._fileChangedSub = this._appStateService.fileChanged.subscribe(() => { + try { this._calculateData(); - }); - this._routerEventsScrollPositionSub = this._router.events - .pipe( - filter( - events => events instanceof NavigationStart || events instanceof NavigationEnd + this._dossierAutoUpdateTimer = timer(0, 10000) + .pipe( + tap(async () => { + await this._appStateService.loadAllDossiers(); + this._loadEntitiesFromState(); + }) ) - ) - .subscribe(event => { - if (event instanceof NavigationStart && event.url !== '/main/dossiers') { - this._lastScrollPosition = this.scrollViewport.measureScrollOffset('top'); - } + .subscribe(); + + this._fileChangedSub = this._appStateService.fileChanged.subscribe(() => { + this._calculateData(); }); + + this._routerEventsScrollPositionSub = this._router.events + .pipe( + filter( + events => + events instanceof NavigationStart || events instanceof NavigationEnd + ) + ) + .subscribe(event => { + if (event instanceof NavigationStart && event.url !== '/main/dossiers') { + this._lastScrollPosition = this.scrollViewport.measureScrollOffset('top'); + } + }); + } catch (e) { + console.log(e); + } } ngOnAttach() { @@ -207,6 +218,10 @@ export class DossierListingScreenComponent this._calculateData(); } + filtersChanged(event) { + this._filterService.filtersChanged(event); + } + protected _preFilter() { this.detailsContainerFilters = { statusFilters: this.statusFilters.map(f => ({ ...f })) @@ -214,12 +229,18 @@ export class DossierListingScreenComponent } private _loadEntitiesFromState() { - this.allEntities = this._appStateService.allDossiers; + this._screenStateService.setEntities(this._appStateService.allDossiers); + } + + private get _user() { + return this._userService.user; } private _calculateData() { this._computeAllFilters(); - this._filterEntities(); + this._filterService.filters = this._filters; + this._filterService.preFilter = () => this._preFilter(); + this._filterService.filterEntities(); this.dossiersChartData = [ { value: this.activeDossiersCount, color: 'ACTIVE', label: 'active' }, { value: this.inactiveDossiersCount, color: 'DELETED', label: 'archived' } @@ -245,7 +266,8 @@ export class DossierListingScreenComponent const allDistinctPeople = new Set(); const allDistinctNeedsWork = new Set(); const allDistinctDossierTemplates = new Set(); - this.allEntities.forEach(entry => { + this._screenStateService.logCurrentState(); + this._screenStateService?.entities?.forEach(entry => { // all people entry.dossier.memberIds.forEach(f => allDistinctPeople.add(f)); // file statuses @@ -342,24 +364,24 @@ export class DossierListingScreenComponent private _createQuickFilters() { const filters: FilterModel[] = [ { - key: this.user.id, + key: this._user.id, label: this._translateService.instant('dossier-listing.quick-filters.my-dossiers'), - checker: (dw: DossierWrapper) => dw.ownerId === this.user.id + checker: (dw: DossierWrapper) => dw.ownerId === this._user.id }, { - key: this.user.id, + key: this._user.id, label: this._translateService.instant('dossier-listing.quick-filters.to-approve'), - checker: (dw: DossierWrapper) => dw.approverIds.includes(this.user.id) + checker: (dw: DossierWrapper) => dw.approverIds.includes(this._user.id) }, { - key: this.user.id, + key: this._user.id, label: this._translateService.instant('dossier-listing.quick-filters.to-review'), - checker: (dw: DossierWrapper) => dw.memberIds.includes(this.user.id) + checker: (dw: DossierWrapper) => dw.memberIds.includes(this._user.id) }, { - key: this.user.id, + key: this._user.id, label: this._translateService.instant('dossier-listing.quick-filters.other'), - checker: (dw: DossierWrapper) => !dw.memberIds.includes(this.user.id) + checker: (dw: DossierWrapper) => !dw.memberIds.includes(this._user.id) } ]; diff --git a/apps/red-ui/src/app/modules/dossier/screens/dossier-overview-screen/dossier-overview-screen.component.html b/apps/red-ui/src/app/modules/dossier/screens/dossier-overview-screen/dossier-overview-screen.component.html index a0cd15405..726486399 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/dossier-overview-screen/dossier-overview-screen.component.html +++ b/apps/red-ui/src/app/modules/dossier/screens/dossier-overview-screen/dossier-overview-screen.component.html @@ -1,11 +1,7 @@
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 8eba4f95a..9ad1a7e6f 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 @@ -40,11 +40,13 @@ import { FilterModel } from '@shared/components/filters/popup-filter/model/filte import { AppConfigKey, AppConfigService } from '../../../app-config/app-config.service'; import { FilterConfig } from '@shared/components/page-header/models/filter-config.model'; import { ActionConfig } from '@shared/components/page-header/models/action-config.model'; +import { FilterService } from '../../../shared/services/filter.service'; @Component({ selector: 'redaction-dossier-overview-screen', templateUrl: './dossier-overview-screen.component.html', - styleUrls: ['./dossier-overview-screen.component.scss'] + styleUrls: ['./dossier-overview-screen.component.scss'], + providers: [FilterService] }) export class DossierOverviewScreenComponent extends BaseListingComponent diff --git a/apps/red-ui/src/app/modules/shared/base/base-listing.component.ts b/apps/red-ui/src/app/modules/shared/base/base-listing.component.ts index d2a1c65a3..9aec7b702 100644 --- a/apps/red-ui/src/app/modules/shared/base/base-listing.component.ts +++ b/apps/red-ui/src/app/modules/shared/base/base-listing.component.ts @@ -1,11 +1,14 @@ import { ChangeDetectorRef, Component, Injector, ViewChild } from '@angular/core'; import { FormBuilder, FormGroup } from '@angular/forms'; -import { debounce } from '@utils/debounce'; import { ScreenName, SortingOption, SortingService } from '@services/sorting.service'; import { FilterModel } from '../components/filters/popup-filter/model/filter.model'; -import { getFilteredEntities } from '../components/filters/popup-filter/utils/filter-utils'; import { QuickFiltersComponent } from '../components/filters/quick-filters/quick-filters.component'; import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling'; +import { FilterService } from '../services/filter.service'; +import { SearchService } from '../services/search.service'; +import { ScreenStateService } from '../services/screen-state.service'; +import { getFilteredEntities } from '../components/filters/popup-filter/utils/filter-utils'; +import { debounce } from '../../../utils/debounce'; // Functionalities: Filter, search, select, sort @@ -24,6 +27,9 @@ export abstract class BaseListingComponent { protected readonly _formBuilder: FormBuilder; protected readonly _changeDetectorRef: ChangeDetectorRef; protected readonly _sortingService: SortingService; + protected readonly _filterService: FilterService; + protected readonly _searchService: SearchService; + protected readonly _screenStateService: ScreenStateService; // ---- // Overwrite in child class: @@ -40,6 +46,9 @@ export abstract class BaseListingComponent { this._formBuilder = this._injector.get(FormBuilder); this._changeDetectorRef = this._injector.get(ChangeDetectorRef); this._sortingService = this._injector.get(SortingService); + this._filterService = this._injector.get>(FilterService); + this._searchService = this._injector.get>(SearchService); + this._screenStateService = this._injector.get>(ScreenStateService); } get areAllEntitiesSelected() { diff --git a/apps/red-ui/src/app/modules/shared/base/new-base-listing.component.ts b/apps/red-ui/src/app/modules/shared/base/new-base-listing.component.ts new file mode 100644 index 000000000..a3e0f4e24 --- /dev/null +++ b/apps/red-ui/src/app/modules/shared/base/new-base-listing.component.ts @@ -0,0 +1,69 @@ +import { ChangeDetectorRef, Component, Injector, ViewChild } from '@angular/core'; +import { ScreenName, SortingOption, SortingService } from '@services/sorting.service'; +import { FilterModel } from '../components/filters/popup-filter/model/filter.model'; +import { QuickFiltersComponent } from '../components/filters/quick-filters/quick-filters.component'; +import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling'; +import { FilterService } from '../services/filter.service'; +import { SearchService } from '../services/search.service'; +import { ScreenStateService } from '../services/screen-state.service'; + +@Component({ template: '' }) +export abstract class NewBaseListingComponent { + @ViewChild(CdkVirtualScrollViewport) scrollViewport: CdkVirtualScrollViewport; + + protected readonly _changeDetectorRef: ChangeDetectorRef; + protected readonly _sortingService: SortingService; + protected readonly _filterService: FilterService; + protected readonly _searchService: SearchService; + protected readonly _screenStateService: ScreenStateService; + + // ---- + // Overwrite in child class: + protected readonly _sortKey: ScreenName; + // Overwrite this in ngOnInit + @ViewChild(QuickFiltersComponent) + protected _quickFilters: QuickFiltersComponent; + + protected constructor(protected readonly _injector: Injector) { + this._changeDetectorRef = this._injector.get(ChangeDetectorRef); + this._sortingService = this._injector.get(SortingService); + this._filterService = this._injector.get>(FilterService); + this._searchService = this._injector.get>(SearchService); + this._screenStateService = this._injector.get>(ScreenStateService); + } + + get sortingOption(): SortingOption { + return this._sortingService.getSortingOption(this._getSortKey); + } + + protected get _filters(): { + values: FilterModel[]; + checker: Function; + matchAll?: boolean; + checkerArgs?: any; + }[] { + return []; + } + + private get _getSortKey(): ScreenName { + if (!this._sortKey) throw new Error('Not implemented'); + + return this._sortKey; + } + + resetFilters() { + this._quickFilters.deactivateFilters(); + this._filterService.reset(); + } + + toggleSort($event) { + this._sortingService.toggleSort(this._getSortKey, $event); + } + + // protected _filterEntities() { + // this._preFilter(); + // this.filteredEntities = getFilteredEntities(this.allEntities, this._filters); + // this.executeSearch(this._searchValue); + // this._changeDetectorRef.detectChanges(); + // } +} diff --git a/apps/red-ui/src/app/modules/shared/components/filters/popup-filter/model/filter-wrapper.model.ts b/apps/red-ui/src/app/modules/shared/components/filters/popup-filter/model/filter-wrapper.model.ts new file mode 100644 index 000000000..9e1a890c5 --- /dev/null +++ b/apps/red-ui/src/app/modules/shared/components/filters/popup-filter/model/filter-wrapper.model.ts @@ -0,0 +1,8 @@ +import { FilterModel } from './filter.model'; + +export interface FilterWrapper { + values: FilterModel[]; + checker: Function; + matchAll?: boolean; + checkerArgs?: any; +} diff --git a/apps/red-ui/src/app/modules/shared/components/filters/popup-filter/utils/filter-utils.ts b/apps/red-ui/src/app/modules/shared/components/filters/popup-filter/utils/filter-utils.ts index 1eb819636..b25d99598 100644 --- a/apps/red-ui/src/app/modules/shared/components/filters/popup-filter/utils/filter-utils.ts +++ b/apps/red-ui/src/app/modules/shared/components/filters/popup-filter/utils/filter-utils.ts @@ -1,7 +1,8 @@ import { FilterModel } from '../model/filter.model'; -import { FileStatusWrapper } from '../../../../../../models/file/file-status.wrapper'; -import { DossierWrapper } from '../../../../../../state/model/dossier.wrapper'; -import { PermissionsService } from '../../../../../../services/permissions.service'; +import { FileStatusWrapper } from '@models/file/file-status.wrapper'; +import { DossierWrapper } from '@state/model/dossier.wrapper'; +import { PermissionsService } from '@services/permissions.service'; +import { FilterWrapper } from '@shared/components/filters/popup-filter/model/filter-wrapper.model'; export function processFilters(oldFilters: FilterModel[], newFilters: FilterModel[]) { copySettings(oldFilters, newFilters); @@ -175,11 +176,8 @@ export const addedDateChecker = (dw: DossierWrapper, filter: FilterModel) => export const dossierApproverChecker = (dw: DossierWrapper, filter: FilterModel) => dw.approverIds.includes(filter.key); -export function getFilteredEntities( - entities: any[], - filters: { values: FilterModel[]; checker: Function; matchAll?: boolean; checkerArgs?: any }[] -) { - const filteredEntities = []; +export function getFilteredEntities(entities: T[], filters: FilterWrapper[]) { + const filteredEntities: T[] = []; for (const entity of entities) { let add = true; for (const filter of filters) { 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 c953293a9..3e960cc89 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 @@ -6,7 +6,7 @@ @@ -50,6 +50,7 @@ > + { @Input() pageLabel: string; @Input() showCloseButton: boolean; - @Input() showResetFilters: boolean; @Input() filterConfigs: FilterConfig[]; @Input() actionConfigs: ActionConfig[]; @Input() buttonConfigs: ButtonConfig[]; @Input() searchPlaceholder: string; - @Output() filtersChanged = new EventEmitter<{ - primary: FilterModel[]; - secondary?: FilterModel[]; - }>(); - @Output() filtersReset = new EventEmitter(); - @Output() searchChanged = new EventEmitter(); - - readonly searchForm = this._formBuilder.group({ - query: [''] - }); @ViewChildren(PopupFilterComponent) private readonly _filterComponents: QueryList; constructor( readonly permissionsService: PermissionsService, - private readonly _formBuilder: FormBuilder - ) { - this.searchForm.valueChanges.subscribe(value => this.searchChanged.emit(value.query)); - } + readonly filterService: FilterService, + readonly searchService: SearchService + ) {} get hasActiveFilters() { const hasActiveFilters = this._filterComponents?.reduce( (acc, component) => acc || component?.hasActiveFilters, false ); - return hasActiveFilters || this.searchForm.get('query').value || this.showResetFilters; + return ( + hasActiveFilters || + this.searchService.searchValue || + this.filterService.showResetFilters + ); } resetFilters() { + this.filterService.reset(); this._filterComponents.forEach(component => component?.deactivateFilters()); - - this.filtersReset.emit(); - this.searchForm.reset({ query: '' }); + this.searchService.reset(); } trackByLabel(index: number, item: BaseHeaderConfig) { 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 new file mode 100644 index 000000000..ef99f938a --- /dev/null +++ b/apps/red-ui/src/app/modules/shared/services/filter.service.ts @@ -0,0 +1,47 @@ +import { ChangeDetectorRef, Injectable } from '@angular/core'; +import { FilterModel } from '@shared/components/filters/popup-filter/model/filter.model'; +import { getFilteredEntities } from '@shared/components/filters/popup-filter/utils/filter-utils'; +import { FilterWrapper } from '@shared/components/filters/popup-filter/model/filter-wrapper.model'; +import { ScreenStateService } from '@shared/services/screen-state.service'; +import { SearchService } from '@shared/services/search.service'; + +@Injectable() +export class FilterService { + showResetFilters = false; + preFilter: () => void; + filters: FilterWrapper[]; + + constructor( + private readonly _screenStateService: ScreenStateService, + private readonly _searchService: SearchService, + private readonly _changeDetector: ChangeDetectorRef + ) {} + + filtersChanged(filters?: { [key: string]: FilterModel[] } | FilterModel[]): void { + console.log(filters); + if (filters instanceof Array) this.showResetFilters = !!filters.find(f => f.checked); + else + for (const key of Object.keys(filters ?? {})) { + for (let idx = 0; idx < this[key]?.length; ++idx) { + this[key][idx] = filters[key][idx]; + } + } + + this.filterEntities(); + } + + filterEntities(): void { + if (this.preFilter) this.preFilter(); + this._screenStateService.setFilteredEntities( + getFilteredEntities(this._screenStateService.entities, this.filters) + ); + this._searchService.executeSearchImmediately(); + this._changeDetector.detectChanges(); + } + + reset(): void { + // this._quickFilters.deactivateFilters(); + this.showResetFilters = false; + this.filtersChanged(); + } +} diff --git a/apps/red-ui/src/app/modules/shared/services/screen-state.service.ts b/apps/red-ui/src/app/modules/shared/services/screen-state.service.ts new file mode 100644 index 000000000..fa0e5d14f --- /dev/null +++ b/apps/red-ui/src/app/modules/shared/services/screen-state.service.ts @@ -0,0 +1,103 @@ +import { Injectable } from '@angular/core'; +import { BehaviorSubject } from 'rxjs'; + +@Injectable() +export class ScreenStateService { + entities$ = new BehaviorSubject>([]); + filteredEntities$ = new BehaviorSubject([]); + displayedEntities$ = new BehaviorSubject([]); + selectedEntitiesIds: string[] = []; + + private _selectionKey: string; + get entities(): T[] { + return Object.values(this.entities$.getValue()); + } + + get filteredEntities(): T[] { + return Object.values(this.filteredEntities$.getValue()); + } + + get displayedEntities(): T[] { + return Object.values(this.displayedEntities$.getValue()); + } + // + // select(mapFn: (state: T[]) => K): Observable { + // return this.entities$.asObservable().pipe( + // map((state: T[]) => mapFn(state)), + // distinctUntilChanged() + // ); + // } + + setEntities(newEntities: Partial) { + this.entities$.next(newEntities); + } + + setFilteredEntities(newEntities: Partial) { + this.filteredEntities$.next(newEntities); + } + + setDisplayedEntities(newEntities: Partial) { + console.log(this.displayedEntities); + this.displayedEntities$.next(newEntities); + console.log(this.displayedEntities); + } + + set selectionKey(value: string) { + this._selectionKey = value; + } + + get areAllEntitiesSelected() { + return ( + this.displayedEntities.length !== 0 && + this.selectedEntitiesIds.length === this.displayedEntities.length + ); + } + + get areSomeEntitiesSelected() { + return this.selectedEntitiesIds.length > 0; + } + + isSelected(entity: T) { + return this.selectedEntitiesIds.indexOf(entity[this._getSelectionKey]) !== -1; + } + + toggleEntitySelected(entity: T) { + const idx = this.selectedEntitiesIds.indexOf(entity[this._getSelectionKey]); + if (idx === -1) { + this.selectedEntitiesIds.push(entity[this._getSelectionKey]); + } else { + this.selectedEntitiesIds.splice(idx, 1); + } + } + + toggleSelectAll() { + if (this.areSomeEntitiesSelected) { + this.selectedEntitiesIds = []; + } else { + this.selectedEntitiesIds = this.displayedEntities.map( + entity => entity[this._getSelectionKey] + ); + } + } + + updateSelection() { + if (this._selectionKey) { + this.selectedEntitiesIds = this.displayedEntities + .map(entity => entity[this._getSelectionKey]) + .filter(id => this.selectedEntitiesIds.includes(id)); + } + } + + logCurrentState() { + console.log('Entities', this.entities); + console.log('Displayed', this.displayedEntities$.getValue()); + console.log('Filtered', this.filteredEntities$.getValue()); + console.log('Selected', this.selectedEntitiesIds); + } + + private get _getSelectionKey(): string { + if (!this._selectionKey) throw new Error('Not implemented'); + + return this._selectionKey; + } +} diff --git a/apps/red-ui/src/app/modules/shared/services/search.service.ts b/apps/red-ui/src/app/modules/shared/services/search.service.ts new file mode 100644 index 000000000..f2e4e94c2 --- /dev/null +++ b/apps/red-ui/src/app/modules/shared/services/search.service.ts @@ -0,0 +1,60 @@ +import { Injectable } from '@angular/core'; +import { debounce } from '@utils/debounce'; +import { ScreenStateService } from '@shared/services/screen-state.service'; +import { FormBuilder } from '@angular/forms'; + +@Injectable() +export class SearchService { + private _searchValue = ''; + private _searchKey: string; + + readonly searchForm = this._formBuilder.group({ + query: [''] + }); + + constructor( + private readonly _screenStateService: ScreenStateService, + private readonly _formBuilder: FormBuilder + ) { + this.searchForm.valueChanges.subscribe(() => this.executeSearch()); + } + + @debounce(200) + executeSearch(): void { + this._searchValue = this.searchValue; + return this.executeSearchImmediately(); + } + + executeSearchImmediately(): void { + const displayed = ( + this._screenStateService.filteredEntities ?? this._screenStateService.entities + ).filter(entity => + this._searchField(entity).toLowerCase().includes(this._searchValue.toLowerCase()) + ); + + this._screenStateService.setDisplayedEntities(displayed); + this._screenStateService.updateSelection(); + } + + set searchKey(value: string) { + this._searchKey = value; + } + + get searchValue(): string { + return this.searchForm.get('query').value; + } + + reset() { + this.searchForm.reset({ query: '' }); + } + + private get _getSearchKey(): string { + if (!this._searchKey) throw new Error('Not implemented'); + + return this._searchKey; + } + + protected _searchField(entity: T): string { + return entity[this._getSearchKey]; + } +} From 5ba302bd8307198e5131d240db106cb13c776d0f Mon Sep 17 00:00:00 2001 From: Dan Percic Date: Fri, 9 Jul 2021 19:24:15 +0300 Subject: [PATCH 10/20] refactor sorting service --- .../active-fields-listing.component.ts | 7 +- ...-attributes-csv-import-dialog.component.ts | 7 +- .../default-colors-screen.component.ts | 10 ++- .../dictionary-listing-screen.component.ts | 10 ++- ...sier-templates-listing-screen.component.ts | 10 ++- ...ile-attributes-listing-screen.component.ts | 14 ++-- .../screens/trash/trash-screen.component.ts | 11 ++- .../user-listing-screen.component.ts | 7 +- .../dossier-listing-screen.component.ts | 63 ++++++++--------- .../dossier-overview-screen.component.ts | 8 ++- .../shared/base/base-listing.component.ts | 13 +--- .../shared/base/new-base-listing.component.ts | 32 ++------- .../src/app/services/sorting.service.ts | 67 +++++++++++++------ 13 files changed, 144 insertions(+), 115 deletions(-) diff --git a/apps/red-ui/src/app/modules/admin/dialogs/file-attributes-csv-import-dialog/active-fields-listing/active-fields-listing.component.ts b/apps/red-ui/src/app/modules/admin/dialogs/file-attributes-csv-import-dialog/active-fields-listing/active-fields-listing.component.ts index c46b2a932..6247c151d 100644 --- a/apps/red-ui/src/app/modules/admin/dialogs/file-attributes-csv-import-dialog/active-fields-listing/active-fields-listing.component.ts +++ b/apps/red-ui/src/app/modules/admin/dialogs/file-attributes-csv-import-dialog/active-fields-listing/active-fields-listing.component.ts @@ -10,11 +10,16 @@ import { import { BaseListingComponent } from '@shared/base/base-listing.component'; import { Field } from '../file-attributes-csv-import-dialog.component'; import { FileAttributeConfig } from '@redaction/red-ui-http'; +import { FilterService } from '../../../../shared/services/filter.service'; +import { SearchService } from '../../../../shared/services/search.service'; +import { ScreenStateService } from '../../../../shared/services/screen-state.service'; +import { SortingService } from '../../../../../services/sorting.service'; @Component({ selector: 'redaction-active-fields-listing', templateUrl: './active-fields-listing.component.html', - styleUrls: ['./active-fields-listing.component.scss'] + styleUrls: ['./active-fields-listing.component.scss'], + providers: [FilterService, SearchService, ScreenStateService, SortingService] }) export class ActiveFieldsListingComponent extends BaseListingComponent implements OnChanges { @Input() allEntities: Field[]; diff --git a/apps/red-ui/src/app/modules/admin/dialogs/file-attributes-csv-import-dialog/file-attributes-csv-import-dialog.component.ts b/apps/red-ui/src/app/modules/admin/dialogs/file-attributes-csv-import-dialog/file-attributes-csv-import-dialog.component.ts index a125a725b..62989ad87 100644 --- a/apps/red-ui/src/app/modules/admin/dialogs/file-attributes-csv-import-dialog/file-attributes-csv-import-dialog.component.ts +++ b/apps/red-ui/src/app/modules/admin/dialogs/file-attributes-csv-import-dialog/file-attributes-csv-import-dialog.component.ts @@ -13,6 +13,10 @@ import { map, startWith } from 'rxjs/operators'; import { BaseListingComponent } from '@shared/base/base-listing.component'; import { NotificationService, NotificationType } from '@services/notification.service'; import { TranslateService } from '@ngx-translate/core'; +import { FilterService } from '../../../shared/services/filter.service'; +import { SearchService } from '../../../shared/services/search.service'; +import { ScreenStateService } from '../../../shared/services/screen-state.service'; +import { SortingService } from '../../../../services/sorting.service'; export interface Field { id?: string; @@ -28,7 +32,8 @@ export interface Field { @Component({ selector: 'redaction-file-attributes-csv-import-dialog', templateUrl: './file-attributes-csv-import-dialog.component.html', - styleUrls: ['./file-attributes-csv-import-dialog.component.scss'] + styleUrls: ['./file-attributes-csv-import-dialog.component.scss'], + providers: [FilterService, SearchService, ScreenStateService, SortingService] }) export class FileAttributesCsvImportDialogComponent extends BaseListingComponent { csvFile: File; diff --git a/apps/red-ui/src/app/modules/admin/screens/default-colors/default-colors-screen.component.ts b/apps/red-ui/src/app/modules/admin/screens/default-colors/default-colors-screen.component.ts index 8c68ef20e..72fea0328 100644 --- a/apps/red-ui/src/app/modules/admin/screens/default-colors/default-colors-screen.component.ts +++ b/apps/red-ui/src/app/modules/admin/screens/default-colors/default-colors-screen.component.ts @@ -6,11 +6,15 @@ import { PermissionsService } from '@services/permissions.service'; import { AdminDialogService } from '../../services/admin-dialog.service'; import { BaseListingComponent } from '@shared/base/base-listing.component'; import { LoadingService } from '../../../../services/loading.service'; +import { FilterService } from '../../../shared/services/filter.service'; +import { SearchService } from '../../../shared/services/search.service'; +import { ScreenStateService } from '../../../shared/services/screen-state.service'; +import { SortingService } from '../../../../services/sorting.service'; @Component({ - selector: 'redaction-default-colors-screen', templateUrl: './default-colors-screen.component.html', - styleUrls: ['./default-colors-screen.component.scss'] + styleUrls: ['./default-colors-screen.component.scss'], + providers: [FilterService, SearchService, ScreenStateService, SortingService] }) export class DefaultColorsScreenComponent extends BaseListingComponent<{ @@ -19,7 +23,6 @@ export class DefaultColorsScreenComponent }> implements OnInit { - protected readonly _sortKey = 'default-colors'; private _colorsObj: Colors; constructor( @@ -32,6 +35,7 @@ export class DefaultColorsScreenComponent protected readonly _injector: Injector ) { super(_injector); + this._sortingService.screenName = 'default-colors'; _appStateService.activateDossierTemplate(_activatedRoute.snapshot.params.dossierTemplateId); } diff --git a/apps/red-ui/src/app/modules/admin/screens/dictionary-listing/dictionary-listing-screen.component.ts b/apps/red-ui/src/app/modules/admin/screens/dictionary-listing/dictionary-listing-screen.component.ts index cc716fbf1..8a6d24c09 100644 --- a/apps/red-ui/src/app/modules/admin/screens/dictionary-listing/dictionary-listing-screen.component.ts +++ b/apps/red-ui/src/app/modules/admin/screens/dictionary-listing/dictionary-listing-screen.component.ts @@ -11,6 +11,10 @@ import { BaseListingComponent } from '@shared/base/base-listing.component'; import { TypeValueWrapper } from '../../../../models/file/type-value.wrapper'; import { TranslateService } from '@ngx-translate/core'; import { LoadingService } from '../../../../services/loading.service'; +import { FilterService } from '../../../shared/services/filter.service'; +import { SearchService } from '../../../shared/services/search.service'; +import { ScreenStateService } from '../../../shared/services/screen-state.service'; +import { SortingService } from '../../../../services/sorting.service'; const toChartConfig = (dict: TypeValueWrapper): DoughnutChartConfig => ({ value: dict.entries ? dict.entries.length : 0, @@ -20,9 +24,9 @@ const toChartConfig = (dict: TypeValueWrapper): DoughnutChartConfig => ({ }); @Component({ - selector: 'redaction-dictionary-listing-screen', templateUrl: './dictionary-listing-screen.component.html', - styleUrls: ['./dictionary-listing-screen.component.scss'] + styleUrls: ['./dictionary-listing-screen.component.scss'], + providers: [FilterService, SearchService, ScreenStateService, SortingService] }) export class DictionaryListingScreenComponent extends BaseListingComponent @@ -31,7 +35,6 @@ export class DictionaryListingScreenComponent chartData: DoughnutChartConfig[] = []; protected readonly _searchKey = 'label'; protected readonly _selectionKey = 'type'; - protected readonly _sortKey = 'dictionary-listing'; constructor( private readonly _dialogService: AdminDialogService, @@ -45,6 +48,7 @@ export class DictionaryListingScreenComponent ) { super(_injector); _loadingService.start(); + this._sortingService.screenName = 'dictionary-listing'; _appStateService.activateDossierTemplate(_activatedRoute.snapshot.params.dossierTemplateId); } diff --git a/apps/red-ui/src/app/modules/admin/screens/dossier-template-listing/dossier-templates-listing-screen.component.ts b/apps/red-ui/src/app/modules/admin/screens/dossier-template-listing/dossier-templates-listing-screen.component.ts index 9b8d64004..d30f94d17 100644 --- a/apps/red-ui/src/app/modules/admin/screens/dossier-template-listing/dossier-templates-listing-screen.component.ts +++ b/apps/red-ui/src/app/modules/admin/screens/dossier-template-listing/dossier-templates-listing-screen.component.ts @@ -7,11 +7,15 @@ import { BaseListingComponent } from '@shared/base/base-listing.component'; import { DossierTemplateModelWrapper } from '../../../../models/file/dossier-template-model.wrapper'; import { LoadingService } from '../../../../services/loading.service'; import { DossierTemplateControllerService } from '@redaction/red-ui-http'; +import { FilterService } from '../../../shared/services/filter.service'; +import { SearchService } from '../../../shared/services/search.service'; +import { ScreenStateService } from '../../../shared/services/screen-state.service'; +import { SortingService } from '../../../../services/sorting.service'; @Component({ - selector: 'redaction-dossier-templates-listing-screen', templateUrl: './dossier-templates-listing-screen.component.html', - styleUrls: ['./dossier-templates-listing-screen.component.scss'] + styleUrls: ['./dossier-templates-listing-screen.component.scss'], + providers: [FilterService, SearchService, ScreenStateService, SortingService] }) export class DossierTemplatesListingScreenComponent extends BaseListingComponent @@ -19,7 +23,6 @@ export class DossierTemplatesListingScreenComponent { protected readonly _searchKey = 'name'; protected readonly _selectionKey = 'dossierTemplateId'; - protected readonly _sortKey = 'dossier-templates-listing'; constructor( private readonly _dialogService: AdminDialogService, @@ -31,6 +34,7 @@ export class DossierTemplatesListingScreenComponent readonly userPreferenceService: UserPreferenceService ) { super(_injector); + this._sortingService.screenName = 'dossier-templates-listing'; } ngOnInit(): void { diff --git a/apps/red-ui/src/app/modules/admin/screens/file-attributes-listing/file-attributes-listing-screen.component.ts b/apps/red-ui/src/app/modules/admin/screens/file-attributes-listing/file-attributes-listing-screen.component.ts index f946643b6..6f1c32839 100644 --- a/apps/red-ui/src/app/modules/admin/screens/file-attributes-listing/file-attributes-listing-screen.component.ts +++ b/apps/red-ui/src/app/modules/admin/screens/file-attributes-listing/file-attributes-listing-screen.component.ts @@ -6,16 +6,19 @@ import { ActivatedRoute } from '@angular/router'; import { AdminDialogService } from '../../services/admin-dialog.service'; import { BaseListingComponent } from '@shared/base/base-listing.component'; import { LoadingService } from '../../../../services/loading.service'; +import { FilterService } from '../../../shared/services/filter.service'; +import { SearchService } from '../../../shared/services/search.service'; +import { ScreenStateService } from '../../../shared/services/screen-state.service'; +import { SortingService } from '../../../../services/sorting.service'; @Component({ - selector: 'redaction-file-attributes-listing-screen', templateUrl: './file-attributes-listing-screen.component.html', - styleUrls: ['./file-attributes-listing-screen.component.scss'] + styleUrls: ['./file-attributes-listing-screen.component.scss'], + providers: [FilterService, SearchService, ScreenStateService, SortingService] }) export class FileAttributesListingScreenComponent extends BaseListingComponent implements OnInit { protected readonly _searchKey = 'label'; protected readonly _selectionKey = 'id'; - protected readonly _sortKey = 'file-attributes-listing'; private _existingConfiguration: FileAttributesConfig; @ViewChild('fileInput') private _fileInput: ElementRef; @@ -30,7 +33,10 @@ export class FileAttributesListingScreenComponent extends BaseListingComponent implements OnInit { readonly itemSize = 85; @@ -20,7 +25,6 @@ export class TrashScreenComponent extends BaseListingComponent implemen protected readonly _searchKey = 'dossierName'; protected readonly _selectionKey = 'dossierId'; - protected readonly _sortKey = 'dossier-listing'; constructor( private readonly _appStateService: AppStateService, @@ -32,6 +36,7 @@ export class TrashScreenComponent extends BaseListingComponent implemen private readonly _translateService: TranslateService ) { super(_injector); + this._sortingService.screenName = 'dossier-listing'; } async ngOnInit(): Promise { @@ -51,7 +56,7 @@ export class TrashScreenComponent extends BaseListingComponent implemen } getRestoreDate(softDeletedTime: string) { - return moment(softDeletedTime).add(5, 'hours').format(); + return moment(softDeletedTime).add(this._deleteRetentionHours, 'hours').format(); } async restore(dossier: Dossier) { diff --git a/apps/red-ui/src/app/modules/admin/screens/user-listing/user-listing-screen.component.ts b/apps/red-ui/src/app/modules/admin/screens/user-listing/user-listing-screen.component.ts index 02d9862d7..8821b604a 100644 --- a/apps/red-ui/src/app/modules/admin/screens/user-listing/user-listing-screen.component.ts +++ b/apps/red-ui/src/app/modules/admin/screens/user-listing/user-listing-screen.component.ts @@ -9,10 +9,15 @@ import { TranslateChartService } from '@services/translate-chart.service'; import { BaseListingComponent } from '@shared/base/base-listing.component'; import { LoadingService } from '../../../../services/loading.service'; import { InitialsAvatarComponent } from '../../../shared/components/initials-avatar/initials-avatar.component'; +import { FilterService } from '../../../shared/services/filter.service'; +import { SearchService } from '../../../shared/services/search.service'; +import { ScreenStateService } from '../../../shared/services/screen-state.service'; +import { SortingService } from '../../../../services/sorting.service'; @Component({ templateUrl: './user-listing-screen.component.html', - styleUrls: ['./user-listing-screen.component.scss'] + styleUrls: ['./user-listing-screen.component.scss'], + providers: [FilterService, SearchService, ScreenStateService, SortingService] }) export class UserListingScreenComponent extends BaseListingComponent implements OnInit { collapsedDetails = false; 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 c1b25a544..2fcfd731a 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 @@ -30,11 +30,13 @@ import { FilterService } from '../../../shared/services/filter.service'; import { SearchService } from '../../../shared/services/search.service'; import { ScreenStateService } from '../../../shared/services/screen-state.service'; import { NewBaseListingComponent } from '../../../shared/base/new-base-listing.component'; +import { FilterWrapper } from '../../../shared/components/filters/popup-filter/model/filter-wrapper.model'; +import { SortingService } from '../../../../services/sorting.service'; @Component({ templateUrl: './dossier-listing-screen.component.html', styleUrls: ['./dossier-listing-screen.component.scss'], - providers: [FilterService, SearchService, ScreenStateService] + providers: [FilterService, SearchService, ScreenStateService, SortingService] }) export class DossierListingScreenComponent extends NewBaseListingComponent @@ -65,8 +67,6 @@ export class DossierListingScreenComponent readonly itemSize = 85; - protected readonly _sortKey = 'dossier-listing'; - private _dossierAutoUpdateTimer: Subscription; private _lastScrollPosition: number; private _routerEventsScrollPositionSub: Subscription; @@ -87,6 +87,7 @@ export class DossierListingScreenComponent protected readonly _injector: Injector ) { super(_injector); + this._sortingService.screenName = 'dossier-listing'; this._appStateService.reset(); this._searchService.searchKey = 'name'; this._loadEntitiesFromState(); @@ -110,12 +111,7 @@ export class DossierListingScreenComponent return this._screenStateService.entities$; } - protected get _filters(): { - values: FilterModel[]; - checker: Function; - matchAll?: boolean; - checkerArgs?: any; - }[] { + protected get _filters(): FilterWrapper[] { return [ { values: this.statusFilters, checker: dossierStatusChecker }, { values: this.peopleFilters, checker: dossierMemberChecker }, @@ -135,37 +131,32 @@ export class DossierListingScreenComponent } ngOnInit(): void { - try { + this._calculateData(); + + this._dossierAutoUpdateTimer = timer(0, 10000) + .pipe( + tap(async () => { + await this._appStateService.loadAllDossiers(); + this._loadEntitiesFromState(); + }) + ) + .subscribe(); + + this._fileChangedSub = this._appStateService.fileChanged.subscribe(() => { this._calculateData(); + }); - this._dossierAutoUpdateTimer = timer(0, 10000) - .pipe( - tap(async () => { - await this._appStateService.loadAllDossiers(); - this._loadEntitiesFromState(); - }) + this._routerEventsScrollPositionSub = this._router.events + .pipe( + filter( + events => events instanceof NavigationStart || events instanceof NavigationEnd ) - .subscribe(); - - this._fileChangedSub = this._appStateService.fileChanged.subscribe(() => { - this._calculateData(); + ) + .subscribe(event => { + if (event instanceof NavigationStart && event.url !== '/main/dossiers') { + this._lastScrollPosition = this.scrollViewport.measureScrollOffset('top'); + } }); - - this._routerEventsScrollPositionSub = this._router.events - .pipe( - filter( - events => - events instanceof NavigationStart || events instanceof NavigationEnd - ) - ) - .subscribe(event => { - if (event instanceof NavigationStart && event.url !== '/main/dossiers') { - this._lastScrollPosition = this.scrollViewport.measureScrollOffset('top'); - } - }); - } catch (e) { - console.log(e); - } } ngOnAttach() { 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 9ad1a7e6f..a1c2d01aa 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 @@ -41,12 +41,14 @@ import { AppConfigKey, AppConfigService } from '../../../app-config/app-config.s import { FilterConfig } from '@shared/components/page-header/models/filter-config.model'; import { ActionConfig } from '@shared/components/page-header/models/action-config.model'; import { FilterService } from '../../../shared/services/filter.service'; +import { SearchService } from '../../../shared/services/search.service'; +import { ScreenStateService } from '../../../shared/services/screen-state.service'; +import { SortingService } from '../../../../services/sorting.service'; @Component({ - selector: 'redaction-dossier-overview-screen', templateUrl: './dossier-overview-screen.component.html', styleUrls: ['./dossier-overview-screen.component.scss'], - providers: [FilterService] + providers: [FilterService, SearchService, ScreenStateService, SortingService] }) export class DossierOverviewScreenComponent extends BaseListingComponent @@ -67,7 +69,6 @@ export class DossierOverviewScreenComponent protected readonly _searchKey = 'searchField'; protected readonly _selectionKey = 'fileId'; - protected readonly _sortKey = 'dossier-overview'; @ViewChild(DossierDetailsComponent, { static: false }) private readonly _dossierDetailsComponent: DossierDetailsComponent; @@ -97,6 +98,7 @@ export class DossierOverviewScreenComponent protected readonly _injector: Injector ) { super(_injector); + this._sortingService.screenName = 'dossier-overview'; this._loadEntitiesFromState(); } diff --git a/apps/red-ui/src/app/modules/shared/base/base-listing.component.ts b/apps/red-ui/src/app/modules/shared/base/base-listing.component.ts index 9aec7b702..1aac87bc6 100644 --- a/apps/red-ui/src/app/modules/shared/base/base-listing.component.ts +++ b/apps/red-ui/src/app/modules/shared/base/base-listing.component.ts @@ -1,6 +1,6 @@ import { ChangeDetectorRef, Component, Injector, ViewChild } from '@angular/core'; import { FormBuilder, FormGroup } from '@angular/forms'; -import { ScreenName, SortingOption, SortingService } from '@services/sorting.service'; +import { SortingOption, SortingService } from '@services/sorting.service'; import { FilterModel } from '../components/filters/popup-filter/model/filter.model'; import { QuickFiltersComponent } from '../components/filters/quick-filters/quick-filters.component'; import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling'; @@ -35,7 +35,6 @@ export abstract class BaseListingComponent { // Overwrite in child class: protected readonly _searchKey: string; protected readonly _selectionKey: string; - protected readonly _sortKey: ScreenName; // Overwrite this in ngOnInit @ViewChild(QuickFiltersComponent) protected _quickFilters: QuickFiltersComponent; @@ -63,7 +62,7 @@ export abstract class BaseListingComponent { } get sortingOption(): SortingOption { - return this._sortingService.getSortingOption(this._getSortKey); + return this._sortingService.getSortingOption(); } protected get _filters(): { @@ -91,12 +90,6 @@ export abstract class BaseListingComponent { return this._selectionKey; } - private get _getSortKey(): ScreenName { - if (!this._sortKey) throw new Error('Not implemented'); - - return this._sortKey; - } - filtersChanged(filters?: { [key: string]: FilterModel[] } | FilterModel[]): void { if (filters instanceof Array) this.showResetFilters = !!filters.find(f => f.checked); else @@ -141,7 +134,7 @@ export abstract class BaseListingComponent { } toggleSort($event) { - this._sortingService.toggleSort(this._getSortKey, $event); + this._sortingService.toggleSort($event); } // Selection diff --git a/apps/red-ui/src/app/modules/shared/base/new-base-listing.component.ts b/apps/red-ui/src/app/modules/shared/base/new-base-listing.component.ts index a3e0f4e24..62af13e62 100644 --- a/apps/red-ui/src/app/modules/shared/base/new-base-listing.component.ts +++ b/apps/red-ui/src/app/modules/shared/base/new-base-listing.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectorRef, Component, Injector, ViewChild } from '@angular/core'; +import { Component, Injector, ViewChild } from '@angular/core'; import { ScreenName, SortingOption, SortingService } from '@services/sorting.service'; import { FilterModel } from '../components/filters/popup-filter/model/filter.model'; import { QuickFiltersComponent } from '../components/filters/quick-filters/quick-filters.component'; @@ -6,26 +6,22 @@ import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling'; import { FilterService } from '../services/filter.service'; import { SearchService } from '../services/search.service'; import { ScreenStateService } from '../services/screen-state.service'; +import { FilterWrapper } from '../components/filters/popup-filter/model/filter-wrapper.model'; @Component({ template: '' }) export abstract class NewBaseListingComponent { @ViewChild(CdkVirtualScrollViewport) scrollViewport: CdkVirtualScrollViewport; - protected readonly _changeDetectorRef: ChangeDetectorRef; protected readonly _sortingService: SortingService; protected readonly _filterService: FilterService; protected readonly _searchService: SearchService; protected readonly _screenStateService: ScreenStateService; - // ---- - // Overwrite in child class: - protected readonly _sortKey: ScreenName; // Overwrite this in ngOnInit @ViewChild(QuickFiltersComponent) protected _quickFilters: QuickFiltersComponent; protected constructor(protected readonly _injector: Injector) { - this._changeDetectorRef = this._injector.get(ChangeDetectorRef); this._sortingService = this._injector.get(SortingService); this._filterService = this._injector.get>(FilterService); this._searchService = this._injector.get>(SearchService); @@ -33,37 +29,19 @@ export abstract class NewBaseListingComponent { } get sortingOption(): SortingOption { - return this._sortingService.getSortingOption(this._getSortKey); + return this._sortingService.getSortingOption(); } - protected get _filters(): { - values: FilterModel[]; - checker: Function; - matchAll?: boolean; - checkerArgs?: any; - }[] { + protected get _filters(): FilterWrapper[] { return []; } - private get _getSortKey(): ScreenName { - if (!this._sortKey) throw new Error('Not implemented'); - - return this._sortKey; - } - resetFilters() { this._quickFilters.deactivateFilters(); this._filterService.reset(); } toggleSort($event) { - this._sortingService.toggleSort(this._getSortKey, $event); + this._sortingService.toggleSort($event); } - - // protected _filterEntities() { - // this._preFilter(); - // this.filteredEntities = getFilteredEntities(this.allEntities, this._filters); - // this.executeSearch(this._searchValue); - // this._changeDetectorRef.detectChanges(); - // } } diff --git a/apps/red-ui/src/app/services/sorting.service.ts b/apps/red-ui/src/app/services/sorting.service.ts index 8e093155f..9ce7f18cb 100644 --- a/apps/red-ui/src/app/services/sorting.service.ts +++ b/apps/red-ui/src/app/services/sorting.service.ts @@ -1,7 +1,14 @@ import { Injectable } from '@angular/core'; -export class SortingOption { - order: 'asc' | 'desc'; +export type SortingOrder = 'asc' | 'desc'; + +export enum SortingOrders { + ASC = 'asc', + DESC = 'desc' +} + +export interface SortingOption { + order: SortingOrder; column: string; } @@ -14,30 +21,50 @@ export type ScreenName = | 'file-attributes-listing' | 'dossier-attributes-listing'; -@Injectable({ - providedIn: 'root' -}) +export enum ScreenNames { + DOSSIER_LISTING = 'dossier-listing', + DOSSIER_OVERVIEW = 'dossier-overview', + DICTIONARY_LISTING = 'dictionary-listing', + DICTIONARY_TEMPLATES_LISTING = 'dossier-templates-listing', + DEFAULT_COLORS = 'default-colors', + FILE_ATTRIBUTES_LISTING = 'file-attributes-listing', + DOSSIER_ATTRIBUTES_LISTING = 'dossier-attributes-listing' +} + +@Injectable() export class SortingService { - private readonly _options: { [key: string]: SortingOption } = { - 'dossier-listing': { column: 'dossier.dossierName', order: 'asc' }, - 'dossier-overview': { column: 'filename', order: 'asc' }, - 'dictionary-listing': { column: 'label', order: 'asc' }, - 'dossier-templates-listing': { column: 'name', order: 'asc' }, - 'default-colors': { column: 'key', order: 'asc' }, - 'file-attributes-listing': { column: 'label', order: 'asc' }, - 'dossier-attributes-listing': { column: 'label', order: 'asc' } + private _currentScreenName: string; + private readonly _options: { [key in ScreenName]: SortingOption } = { + [ScreenNames.DOSSIER_LISTING]: { column: 'dossier.dossierName', order: SortingOrders.ASC }, + [ScreenNames.DOSSIER_OVERVIEW]: { column: 'filename', order: SortingOrders.ASC }, + [ScreenNames.DICTIONARY_LISTING]: { column: 'label', order: SortingOrders.ASC }, + [ScreenNames.DICTIONARY_TEMPLATES_LISTING]: { column: 'name', order: SortingOrders.ASC }, + [ScreenNames.DEFAULT_COLORS]: { column: 'key', order: SortingOrders.ASC }, + [ScreenNames.FILE_ATTRIBUTES_LISTING]: { column: 'label', order: SortingOrders.ASC }, + [ScreenNames.DOSSIER_ATTRIBUTES_LISTING]: { column: 'label', order: 'asc' } }; - toggleSort(screen: ScreenName, column: string) { - if (this._options[screen].column === column) { - const currentOrder = this._options[screen].order; - this._options[screen].order = currentOrder === 'asc' ? 'desc' : 'asc'; + set screenName(value: string) { + this._currentScreenName = value; + } + + toggleSort(column: string) { + if (this._options[this._currentScreenName].column === column) { + this._currentOrder = this._currentOrder === SortingOrders.ASC ? SortingOrders.DESC : SortingOrders.ASC; } else { - this._options[screen] = { column, order: 'asc' }; + this._options[this._currentScreenName] = { column, order: SortingOrders.ASC }; } } - getSortingOption(screen: ScreenName) { - return this._options[screen]; + getSortingOption() { + return this._options[this._currentScreenName]; + } + + private get _currentOrder(): string { + return this._options[this._currentScreenName].order; + } + + private set _currentOrder(value: string) { + this._options[this._currentScreenName].order = value; } } From 13d8a7da04ae36fde40fb84b0abef11d9b6e28fe Mon Sep 17 00:00:00 2001 From: Dan Percic Date: Sun, 11 Jul 2021 21:28:56 +0300 Subject: [PATCH 11/20] fix error handler, change listing details filters --- .../dossier-listing-details.component.html | 8 ++--- .../dossier-listing-details.component.ts | 16 +++------- .../dossier-listing-screen.component.html | 8 ++--- .../dossier-listing-screen.component.ts | 29 +++++-------------- .../components/sort-pipe/sort-by.pipe.ts | 3 +- .../shared/services/screen-state.service.ts | 2 -- 6 files changed, 20 insertions(+), 46 deletions(-) diff --git a/apps/red-ui/src/app/modules/dossier/components/dossier-listing-details/dossier-listing-details.component.html b/apps/red-ui/src/app/modules/dossier/components/dossier-listing-details/dossier-listing-details.component.html index 3a56278b7..1487103d7 100644 --- a/apps/red-ui/src/app/modules/dossier/components/dossier-listing-details/dossier-listing-details.component.html +++ b/apps/red-ui/src/app/modules/dossier/components/dossier-listing-details/dossier-listing-details.component.html @@ -10,7 +10,7 @@
-
{{ totalPages | number }}
+
{{ appStateService.totalAnalysedPages | number }}
@@ -18,7 +18,7 @@
-
{{ totalPeople }}
+
{{ appStateService.totalPeople }}
@@ -26,9 +26,9 @@
f.key === key); + toggleFilter(key: string): void { + const filter = this.filters.find(f => f.key === key); filter.checked = !filter.checked; this.filtersChanged.emit(this.filters); } diff --git a/apps/red-ui/src/app/modules/dossier/screens/dossier-listing-screen/dossier-listing-screen.component.html b/apps/red-ui/src/app/modules/dossier/screens/dossier-listing-screen/dossier-listing-screen.component.html index 89ce05585..40250c3da 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/dossier-listing-screen/dossier-listing-screen.component.html +++ b/apps/red-ui/src/app/modules/dossier/screens/dossier-listing-screen/dossier-listing-screen.component.html @@ -69,10 +69,8 @@ | async | sortBy: sortingOption.order:sortingOption.column " - [class.pointer]="canOpenDossier(dw)" - [routerLink]="[ - canOpenDossier(dw) ? '/main/dossiers/' + dw.dossier.dossierId : [] - ]" + [class.pointer]="!!dw" + [routerLink]="[!!dw ? '/main/dossiers/' + dw.dossier.dossierId : []]" class="table-item" >
@@ -100,7 +98,7 @@
- {{ userCount(dw) }} + {{ dw.numberOfMembers }}
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 2fcfd731a..ad1d502fb 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 @@ -7,7 +7,7 @@ import { groupBy } from '@utils/functions'; import { TranslateService } from '@ngx-translate/core'; import { PermissionsService } from '@services/permissions.service'; import { DossierWrapper } from '@state/model/dossier.wrapper'; -import { Observable, Subscription, timer } from 'rxjs'; +import { Subscription, timer } from 'rxjs'; import { filter, tap } from 'rxjs/operators'; import { TranslateChartService } from '@services/translate-chart.service'; import { RedactionFilterSorter } from '@utils/sorters/redaction-filter-sorter'; @@ -31,7 +31,7 @@ import { SearchService } from '../../../shared/services/search.service'; import { ScreenStateService } from '../../../shared/services/screen-state.service'; import { NewBaseListingComponent } from '../../../shared/base/new-base-listing.component'; import { FilterWrapper } from '../../../shared/components/filters/popup-filter/model/filter-wrapper.model'; -import { SortingService } from '../../../../services/sorting.service'; +import { ScreenNames, SortingService } from '../../../../services/sorting.service'; @Component({ templateUrl: './dossier-listing-screen.component.html', @@ -48,11 +48,7 @@ export class DossierListingScreenComponent peopleFilters: FilterModel[]; needsWorkFilters: FilterModel[]; dossierTemplateFilters: FilterModel[]; - detailsContainerFilters: { - statusFilters: FilterModel[]; - } = { - statusFilters: [] - }; + detailsContainerFilters: FilterModel[] = []; quickFilters: FilterModel[]; filterConfigs: FilterConfig[]; buttonConfigs: ButtonConfig[] = [ @@ -87,13 +83,14 @@ export class DossierListingScreenComponent protected readonly _injector: Injector ) { super(_injector); - this._sortingService.screenName = 'dossier-listing'; + this._sortingService.screenName = ScreenNames.DOSSIER_LISTING; this._appStateService.reset(); this._searchService.searchKey = 'name'; this._loadEntitiesFromState(); } get activeDossiersCount(): number { + console.log('active dossiers'); return this._screenStateService.entities.filter( p => p.dossier.status === Dossier.StatusEnum.ACTIVE ).length; @@ -180,14 +177,6 @@ export class DossierListingScreenComponent return dossier.files.length; } - userCount(dossier: DossierWrapper) { - return dossier.numberOfMembers; - } - - canOpenDossier(dw: DossierWrapper): boolean { - return !!dw; - } - getDossierTemplate(dw: DossierWrapper): DossierTemplateModel { return this._appStateService.getDossierTemplateById(dw.dossier.dossierTemplateId); } @@ -214,9 +203,7 @@ export class DossierListingScreenComponent } protected _preFilter() { - this.detailsContainerFilters = { - statusFilters: this.statusFilters.map(f => ({ ...f })) - }; + this.detailsContainerFilters = this.statusFilters.map(f => ({ ...f })); } private _loadEntitiesFromState() { @@ -257,14 +244,12 @@ export class DossierListingScreenComponent const allDistinctPeople = new Set(); const allDistinctNeedsWork = new Set(); const allDistinctDossierTemplates = new Set(); - this._screenStateService.logCurrentState(); this._screenStateService?.entities?.forEach(entry => { // all people entry.dossier.memberIds.forEach(f => allDistinctPeople.add(f)); - // file statuses - entry.files.forEach(f => allDistinctFileStatus.add(f.status)); // Needs work entry.files.forEach(file => { + allDistinctFileStatus.add(file.status); if (this.permissionsService.fileRequiresReanalysis(file)) allDistinctNeedsWork.add('analysis'); if (entry.hintsOnly) allDistinctNeedsWork.add('hint'); diff --git a/apps/red-ui/src/app/modules/shared/components/sort-pipe/sort-by.pipe.ts b/apps/red-ui/src/app/modules/shared/components/sort-pipe/sort-by.pipe.ts index 3500ec404..48a3aee2f 100644 --- a/apps/red-ui/src/app/modules/shared/components/sort-pipe/sort-by.pipe.ts +++ b/apps/red-ui/src/app/modules/shared/components/sort-pipe/sort-by.pipe.ts @@ -1,5 +1,6 @@ import { Pipe, PipeTransform } from '@angular/core'; import { orderBy } from 'lodash'; +import { SortingOrders } from '@services/sorting.service'; @Pipe({ name: 'sortBy' }) export class SortByPipe implements PipeTransform { @@ -8,7 +9,7 @@ export class SortByPipe implements PipeTransform { return value; } // no array if (!column || column === '') { - if (order === 'asc') { + if (order === SortingOrders.ASC) { return value.sort(); } else { return value.sort().reverse(); diff --git a/apps/red-ui/src/app/modules/shared/services/screen-state.service.ts b/apps/red-ui/src/app/modules/shared/services/screen-state.service.ts index fa0e5d14f..8c7965292 100644 --- a/apps/red-ui/src/app/modules/shared/services/screen-state.service.ts +++ b/apps/red-ui/src/app/modules/shared/services/screen-state.service.ts @@ -37,9 +37,7 @@ export class ScreenStateService { } setDisplayedEntities(newEntities: Partial) { - console.log(this.displayedEntities); this.displayedEntities$.next(newEntities); - console.log(this.displayedEntities); } set selectionKey(value: string) { From 204cdc7787a08462bcc10a0bf50c8e4afe5fa373 Mon Sep 17 00:00:00 2001 From: Dan Percic Date: Mon, 12 Jul 2021 00:29:05 +0300 Subject: [PATCH 12/20] update filter, sorting and search services --- .../default-colors-screen.component.ts | 4 +-- .../dictionary-listing-screen.component.ts | 4 +-- ...sier-templates-listing-screen.component.ts | 4 +-- ...ile-attributes-listing-screen.component.ts | 4 +-- .../screens/trash/trash-screen.component.ts | 10 +++++-- .../dossier-listing-screen.component.ts | 11 +++---- .../dossier-overview-screen.component.ts | 4 +-- .../modules/shared/services/filter.service.ts | 29 ++++++++++++++----- .../shared/services/screen-state.service.ts | 12 ++++---- .../modules/shared/services/search.service.ts | 10 +++---- .../src/app/services/sorting.service.ts | 6 ++-- 11 files changed, 59 insertions(+), 39 deletions(-) diff --git a/apps/red-ui/src/app/modules/admin/screens/default-colors/default-colors-screen.component.ts b/apps/red-ui/src/app/modules/admin/screens/default-colors/default-colors-screen.component.ts index 72fea0328..a6ecb91dc 100644 --- a/apps/red-ui/src/app/modules/admin/screens/default-colors/default-colors-screen.component.ts +++ b/apps/red-ui/src/app/modules/admin/screens/default-colors/default-colors-screen.component.ts @@ -9,7 +9,7 @@ import { LoadingService } from '../../../../services/loading.service'; import { FilterService } from '../../../shared/services/filter.service'; import { SearchService } from '../../../shared/services/search.service'; import { ScreenStateService } from '../../../shared/services/screen-state.service'; -import { SortingService } from '../../../../services/sorting.service'; +import { ScreenNames, SortingService } from '../../../../services/sorting.service'; @Component({ templateUrl: './default-colors-screen.component.html', @@ -35,7 +35,7 @@ export class DefaultColorsScreenComponent protected readonly _injector: Injector ) { super(_injector); - this._sortingService.screenName = 'default-colors'; + this._sortingService.setScreenName(ScreenNames.DEFAULT_COLORS); _appStateService.activateDossierTemplate(_activatedRoute.snapshot.params.dossierTemplateId); } diff --git a/apps/red-ui/src/app/modules/admin/screens/dictionary-listing/dictionary-listing-screen.component.ts b/apps/red-ui/src/app/modules/admin/screens/dictionary-listing/dictionary-listing-screen.component.ts index 8a6d24c09..1c22df244 100644 --- a/apps/red-ui/src/app/modules/admin/screens/dictionary-listing/dictionary-listing-screen.component.ts +++ b/apps/red-ui/src/app/modules/admin/screens/dictionary-listing/dictionary-listing-screen.component.ts @@ -14,7 +14,7 @@ import { LoadingService } from '../../../../services/loading.service'; import { FilterService } from '../../../shared/services/filter.service'; import { SearchService } from '../../../shared/services/search.service'; import { ScreenStateService } from '../../../shared/services/screen-state.service'; -import { SortingService } from '../../../../services/sorting.service'; +import { ScreenNames, SortingService } from '../../../../services/sorting.service'; const toChartConfig = (dict: TypeValueWrapper): DoughnutChartConfig => ({ value: dict.entries ? dict.entries.length : 0, @@ -48,7 +48,7 @@ export class DictionaryListingScreenComponent ) { super(_injector); _loadingService.start(); - this._sortingService.screenName = 'dictionary-listing'; + this._sortingService.setScreenName(ScreenNames.DOSSIER_LISTING); _appStateService.activateDossierTemplate(_activatedRoute.snapshot.params.dossierTemplateId); } diff --git a/apps/red-ui/src/app/modules/admin/screens/dossier-template-listing/dossier-templates-listing-screen.component.ts b/apps/red-ui/src/app/modules/admin/screens/dossier-template-listing/dossier-templates-listing-screen.component.ts index d30f94d17..1b0a67993 100644 --- a/apps/red-ui/src/app/modules/admin/screens/dossier-template-listing/dossier-templates-listing-screen.component.ts +++ b/apps/red-ui/src/app/modules/admin/screens/dossier-template-listing/dossier-templates-listing-screen.component.ts @@ -10,7 +10,7 @@ import { DossierTemplateControllerService } from '@redaction/red-ui-http'; import { FilterService } from '../../../shared/services/filter.service'; import { SearchService } from '../../../shared/services/search.service'; import { ScreenStateService } from '../../../shared/services/screen-state.service'; -import { SortingService } from '../../../../services/sorting.service'; +import { ScreenNames, SortingService } from '../../../../services/sorting.service'; @Component({ templateUrl: './dossier-templates-listing-screen.component.html', @@ -34,7 +34,7 @@ export class DossierTemplatesListingScreenComponent readonly userPreferenceService: UserPreferenceService ) { super(_injector); - this._sortingService.screenName = 'dossier-templates-listing'; + this._sortingService.setScreenName(ScreenNames.DOSSIER_TEMPLATES_LISTING); } ngOnInit(): void { diff --git a/apps/red-ui/src/app/modules/admin/screens/file-attributes-listing/file-attributes-listing-screen.component.ts b/apps/red-ui/src/app/modules/admin/screens/file-attributes-listing/file-attributes-listing-screen.component.ts index 6f1c32839..5b43c6f99 100644 --- a/apps/red-ui/src/app/modules/admin/screens/file-attributes-listing/file-attributes-listing-screen.component.ts +++ b/apps/red-ui/src/app/modules/admin/screens/file-attributes-listing/file-attributes-listing-screen.component.ts @@ -9,7 +9,7 @@ import { LoadingService } from '../../../../services/loading.service'; import { FilterService } from '../../../shared/services/filter.service'; import { SearchService } from '../../../shared/services/search.service'; import { ScreenStateService } from '../../../shared/services/screen-state.service'; -import { SortingService } from '../../../../services/sorting.service'; +import { ScreenNames, SortingService } from '../../../../services/sorting.service'; @Component({ templateUrl: './file-attributes-listing-screen.component.html', @@ -33,7 +33,7 @@ export class FileAttributesListingScreenComponent extends BaseListingComponent implemen private readonly _translateService: TranslateService ) { super(_injector); - this._sortingService.screenName = 'dossier-listing'; + this._sortingService.setScreenName(ScreenNames.DOSSIER_LISTING); + this._searchService.setSearchKey('dossierName'); + this._screenStateService.setSelectionKey('dossierId'); } async ngOnInit(): Promise { this._loadingService.start(); + await this.loadDossierTemplatesData(); + this._filterService.setFilters(this._filters); + this._filterService.filterEntities(); + this._loadingService.stop(); } 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 ad1d502fb..d4aefdd9c 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 @@ -83,14 +83,13 @@ export class DossierListingScreenComponent protected readonly _injector: Injector ) { super(_injector); - this._sortingService.screenName = ScreenNames.DOSSIER_LISTING; + this._sortingService.setScreenName(ScreenNames.DOSSIER_LISTING); + this._searchService.setSearchKey('name'); this._appStateService.reset(); - this._searchService.searchKey = 'name'; this._loadEntitiesFromState(); } get activeDossiersCount(): number { - console.log('active dossiers'); return this._screenStateService.entities.filter( p => p.dossier.status === Dossier.StatusEnum.ACTIVE ).length; @@ -216,9 +215,11 @@ export class DossierListingScreenComponent private _calculateData() { this._computeAllFilters(); - this._filterService.filters = this._filters; - this._filterService.preFilter = () => this._preFilter(); + + this._filterService.setFilters(this._filters); + this._filterService.setPreFilters(() => this._preFilter()); this._filterService.filterEntities(); + this.dossiersChartData = [ { value: this.activeDossiersCount, color: 'ACTIVE', label: 'active' }, { value: this.inactiveDossiersCount, color: 'DELETED', label: 'archived' } 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 a1c2d01aa..d64614254 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 @@ -43,7 +43,7 @@ import { ActionConfig } from '@shared/components/page-header/models/action-confi import { FilterService } from '../../../shared/services/filter.service'; import { SearchService } from '../../../shared/services/search.service'; import { ScreenStateService } from '../../../shared/services/screen-state.service'; -import { SortingService } from '../../../../services/sorting.service'; +import { ScreenNames, SortingService } from '../../../../services/sorting.service'; @Component({ templateUrl: './dossier-overview-screen.component.html', @@ -98,7 +98,7 @@ export class DossierOverviewScreenComponent protected readonly _injector: Injector ) { super(_injector); - this._sortingService.screenName = 'dossier-overview'; + this._sortingService.setScreenName(ScreenNames.DOSSIER_OVERVIEW); this._loadEntitiesFromState(); } 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 ef99f938a..e8439ef09 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 @@ -8,8 +8,8 @@ import { SearchService } from '@shared/services/search.service'; @Injectable() export class FilterService { showResetFilters = false; - preFilter: () => void; - filters: FilterWrapper[]; + _preFilter: () => void; + _filters: FilterWrapper[]; constructor( private readonly _screenStateService: ScreenStateService, @@ -17,8 +17,21 @@ export class FilterService { private readonly _changeDetector: ChangeDetectorRef ) {} + setFilters(value: FilterWrapper[]) { + this._filters = value; + } + + setPreFilters(value: () => void) { + this._preFilter = value; + } + + get filters(): FilterWrapper[] { + if (this._filters === undefined || this._filters === null) throw new Error('Unset filters'); + + return this._filters; + } + filtersChanged(filters?: { [key: string]: FilterModel[] } | FilterModel[]): void { - console.log(filters); if (filters instanceof Array) this.showResetFilters = !!filters.find(f => f.checked); else for (const key of Object.keys(filters ?? {})) { @@ -31,16 +44,16 @@ export class FilterService { } filterEntities(): void { - if (this.preFilter) this.preFilter(); - this._screenStateService.setFilteredEntities( - getFilteredEntities(this._screenStateService.entities, this.filters) - ); + if (this._preFilter) this._preFilter(); + + const filtered = getFilteredEntities(this._screenStateService.entities, this.filters); + this._screenStateService.setFilteredEntities(filtered); this._searchService.executeSearchImmediately(); + this._changeDetector.detectChanges(); } reset(): void { - // this._quickFilters.deactivateFilters(); this.showResetFilters = false; this.filtersChanged(); } diff --git a/apps/red-ui/src/app/modules/shared/services/screen-state.service.ts b/apps/red-ui/src/app/modules/shared/services/screen-state.service.ts index 8c7965292..70ea4382b 100644 --- a/apps/red-ui/src/app/modules/shared/services/screen-state.service.ts +++ b/apps/red-ui/src/app/modules/shared/services/screen-state.service.ts @@ -8,7 +8,7 @@ export class ScreenStateService { displayedEntities$ = new BehaviorSubject([]); selectedEntitiesIds: string[] = []; - private _selectionKey: string; + private _setSelectionKey: string; get entities(): T[] { return Object.values(this.entities$.getValue()); } @@ -40,8 +40,8 @@ export class ScreenStateService { this.displayedEntities$.next(newEntities); } - set selectionKey(value: string) { - this._selectionKey = value; + setSelectionKey(value: string) { + this._setSelectionKey = value; } get areAllEntitiesSelected() { @@ -79,7 +79,7 @@ export class ScreenStateService { } updateSelection() { - if (this._selectionKey) { + if (this._setSelectionKey) { this.selectedEntitiesIds = this.displayedEntities .map(entity => entity[this._getSelectionKey]) .filter(id => this.selectedEntitiesIds.includes(id)); @@ -94,8 +94,8 @@ export class ScreenStateService { } private get _getSelectionKey(): string { - if (!this._selectionKey) throw new Error('Not implemented'); + if (!this._setSelectionKey) throw new Error('Not implemented'); - return this._selectionKey; + return this._setSelectionKey; } } diff --git a/apps/red-ui/src/app/modules/shared/services/search.service.ts b/apps/red-ui/src/app/modules/shared/services/search.service.ts index f2e4e94c2..4c75ebc54 100644 --- a/apps/red-ui/src/app/modules/shared/services/search.service.ts +++ b/apps/red-ui/src/app/modules/shared/services/search.service.ts @@ -6,7 +6,7 @@ import { FormBuilder } from '@angular/forms'; @Injectable() export class SearchService { private _searchValue = ''; - private _searchKey: string; + private _setSearchKey: string; readonly searchForm = this._formBuilder.group({ query: [''] @@ -36,8 +36,8 @@ export class SearchService { this._screenStateService.updateSelection(); } - set searchKey(value: string) { - this._searchKey = value; + setSearchKey(value: string) { + this._setSearchKey = value; } get searchValue(): string { @@ -49,9 +49,9 @@ export class SearchService { } private get _getSearchKey(): string { - if (!this._searchKey) throw new Error('Not implemented'); + if (!this._setSearchKey) throw new Error('Not implemented'); - return this._searchKey; + return this._setSearchKey; } protected _searchField(entity: T): string { diff --git a/apps/red-ui/src/app/services/sorting.service.ts b/apps/red-ui/src/app/services/sorting.service.ts index 9ce7f18cb..b2a07b5db 100644 --- a/apps/red-ui/src/app/services/sorting.service.ts +++ b/apps/red-ui/src/app/services/sorting.service.ts @@ -25,7 +25,7 @@ export enum ScreenNames { DOSSIER_LISTING = 'dossier-listing', DOSSIER_OVERVIEW = 'dossier-overview', DICTIONARY_LISTING = 'dictionary-listing', - DICTIONARY_TEMPLATES_LISTING = 'dossier-templates-listing', + DOSSIER_TEMPLATES_LISTING = 'dossier-templates-listing', DEFAULT_COLORS = 'default-colors', FILE_ATTRIBUTES_LISTING = 'file-attributes-listing', DOSSIER_ATTRIBUTES_LISTING = 'dossier-attributes-listing' @@ -38,13 +38,13 @@ export class SortingService { [ScreenNames.DOSSIER_LISTING]: { column: 'dossier.dossierName', order: SortingOrders.ASC }, [ScreenNames.DOSSIER_OVERVIEW]: { column: 'filename', order: SortingOrders.ASC }, [ScreenNames.DICTIONARY_LISTING]: { column: 'label', order: SortingOrders.ASC }, - [ScreenNames.DICTIONARY_TEMPLATES_LISTING]: { column: 'name', order: SortingOrders.ASC }, + [ScreenNames.DOSSIER_TEMPLATES_LISTING]: { column: 'name', order: SortingOrders.ASC }, [ScreenNames.DEFAULT_COLORS]: { column: 'key', order: SortingOrders.ASC }, [ScreenNames.FILE_ATTRIBUTES_LISTING]: { column: 'label', order: SortingOrders.ASC }, [ScreenNames.DOSSIER_ATTRIBUTES_LISTING]: { column: 'label', order: 'asc' } }; - set screenName(value: string) { + setScreenName(value: string) { this._currentScreenName = value; } From 8f22539cfefab23fceaa1cf64acbe72648bacda1 Mon Sep 17 00:00:00 2001 From: Dan Percic Date: Wed, 14 Jul 2021 01:40:41 +0300 Subject: [PATCH 13/20] refactor dossier listing and dossier overview, working filters --- .../screens/trash/trash-screen.component.html | 16 +- .../screens/trash/trash-screen.component.ts | 63 +++-- .../dossier-details.component.html | 10 +- .../dossier-details.component.ts | 14 +- .../dossier-listing-details.component.html | 3 +- .../dossier-listing-details.component.ts | 22 +- .../scroll-button.component.html | 8 +- .../scroll-button/scroll-button.component.ts | 2 +- .../app/modules/dossier/dossiers.module.ts | 2 + .../dossier-listing-screen.component.html | 22 +- .../dossier-listing-screen.component.ts | 261 +++++++----------- .../dossier-overview-screen.component.html | 25 +- .../dossier-overview-screen.component.ts | 178 +++++------- .../dossier/services/dossiers.service.ts | 21 ++ .../shared/base/base-listing.component.ts | 11 +- .../shared/base/new-base-listing.component.ts | 59 +++- .../model/filter-wrapper.model.ts | 6 + .../popup-filter/popup-filter.component.ts | 2 - .../quick-filters.component.html | 4 +- .../quick-filters/quick-filters.component.ts | 17 +- .../full-page-loading-indicator.component.ts | 5 +- .../page-header/models/filter-config.model.ts | 3 +- .../page-header/page-header.component.html | 10 +- .../page-header/page-header.component.ts | 28 +- .../simple-doughnut-chart.component.html | 8 +- .../simple-doughnut-chart.component.ts | 25 +- .../modules/shared/services/filter.service.ts | 90 ++++-- .../shared/services/screen-state.service.ts | 103 ++++--- .../modules/shared/services/search.service.ts | 22 +- .../src/app/services/loading.service.ts | 29 +- .../utils/sorters/redaction-filter-sorter.ts | 4 +- .../src/app/utils/sorters/status-sorter.ts | 3 +- .../src/lib/api/dossierController.service.ts | 19 +- 33 files changed, 545 insertions(+), 550 deletions(-) create mode 100644 apps/red-ui/src/app/modules/dossier/services/dossiers.service.ts diff --git a/apps/red-ui/src/app/modules/admin/screens/trash/trash-screen.component.html b/apps/red-ui/src/app/modules/admin/screens/trash/trash-screen.component.html index e8f0dd7dc..995d027c0 100644 --- a/apps/red-ui/src/app/modules/admin/screens/trash/trash-screen.component.html +++ b/apps/red-ui/src/app/modules/admin/screens/trash/trash-screen.component.html @@ -19,7 +19,8 @@ {{ - 'trash.table-header.title' | translate: { length: displayedEntities.length } + 'trash.table-header.title' + | translate: { length: (displayedEntities$ | async)?.length } }} @@ -40,7 +41,7 @@
@@ -75,13 +76,13 @@
@@ -89,7 +90,8 @@
implements OnInit { +export class TrashScreenComponent extends NewBaseListingComponent implements OnInit { readonly itemSize = 85; private readonly _deleteRetentionHours = this._appConfigService.getConfig( AppConfigKey.DELETE_RETENTION_HOURS ); - protected readonly _searchKey = 'dossierName'; - protected readonly _selectionKey = 'dossierId'; - constructor( private readonly _appStateService: AppStateService, readonly permissionsService: PermissionsService, protected readonly _injector: Injector, - private readonly _dossierControllerService: DossierControllerService, + private readonly _dossiersService: DossiersService, private readonly _loadingService: LoadingService, - private readonly _appConfigService: AppConfigService, - private readonly _translateService: TranslateService + private readonly _appConfigService: AppConfigService ) { super(_injector); this._sortingService.setScreenName(ScreenNames.DOSSIER_LISTING); this._searchService.setSearchKey('dossierName'); - this._screenStateService.setSelectionKey('dossierId'); + this._screenStateService.setIdKey('dossierId'); } async ngOnInit(): Promise { this._loadingService.start(); await this.loadDossierTemplatesData(); - this._filterService.setFilters(this._filters); - this._filterService.filterEntities(); + this.filterService.filterEntities(); this._loadingService.stop(); } async loadDossierTemplatesData(): Promise { - this.allEntities = await this._dossierControllerService.getDeletedDossiers().toPromise(); - console.log(this.allEntities); - this._executeSearchImmediately(); + this._screenStateService.setEntities(await this._dossiersService.getDeletedDossiers()); } getDossierTemplate(dossierTemplateId: string): DossierTemplateModel { return this._appStateService.getDossierTemplateById(dossierTemplateId); } - getRestoreDate(softDeletedTime: string) { - return moment(softDeletedTime).add(this._deleteRetentionHours, 'hours').format(); + getRestoreDate(softDeletedTime: string): string { + return moment(softDeletedTime).add(this._deleteRetentionHours, 'hours').toISOString(); } - async restore(dossier: Dossier) { - this._loadingService.start(); - await this._dossierControllerService.restoreDossiers([dossier.dossierId]).toPromise(); - this.allEntities = this.allEntities.filter(e => e !== dossier); - this._loadingService.stop(); + restore(dossierId: string): void { + this._loadingService.loadWhile(this._restore(dossierId)); } - async hardDelete(dossier: Dossier) { - this._loadingService.start(); - await this._dossierControllerService.hardDeleteDossiers([dossier.dossierId]).toPromise(); - this.allEntities = this.allEntities.filter(e => e !== dossier); - this._loadingService.stop(); + hardDelete(dossierId: string): void { + this._loadingService.loadWhile(this._hardDelete(dossierId)); } - trackById(index: number, dossier: Dossier) { + trackById(index: number, dossier: Dossier): string { return dossier.dossierId; } + + private async _restore(dossierId: string): Promise { + await this._dossiersService.restore(dossierId); + this._removeFromList(dossierId); + } + + private async _hardDelete(dossierId: string): Promise { + await this._dossiersService.hardDelete(dossierId); + this._removeFromList(dossierId); + } + + private _removeFromList(dossierId: string): void { + const entities = this._screenStateService.entities.filter(e => e.dossierId !== dossierId); + this._screenStateService.setEntities(entities); + this.filterService.filterEntities(); + } } 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 760dddfcd..11992f128 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 @@ -45,9 +45,9 @@
diff --git a/apps/red-ui/src/app/modules/dossier/components/dossier-details/dossier-details.component.ts b/apps/red-ui/src/app/modules/dossier/components/dossier-details/dossier-details.component.ts index 4d0212f09..2f3e35e54 100644 --- a/apps/red-ui/src/app/modules/dossier/components/dossier-details/dossier-details.component.ts +++ b/apps/red-ui/src/app/modules/dossier/components/dossier-details/dossier-details.component.ts @@ -1,14 +1,15 @@ -import { ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { ChangeDetectorRef, Component, EventEmitter, OnInit, Output } from '@angular/core'; import { AppStateService } from '@state/app-state.service'; import { groupBy } from '@utils/functions'; import { DoughnutChartConfig } from '@shared/components/simple-doughnut-chart/simple-doughnut-chart.component'; import { PermissionsService } from '@services/permissions.service'; import { TranslateChartService } from '@services/translate-chart.service'; import { StatusSorter } from '@utils/sorters/status-sorter'; -import { FilterModel } from '@shared/components/filters/popup-filter/model/filter.model'; import { UserService } from '@services/user.service'; import { User } from '@redaction/red-ui-http'; import { NotificationService } from '@services/notification.service'; +import { FilterService } from '../../../shared/services/filter.service'; +import { FileStatusWrapper } from '../../../../models/file/file-status.wrapper'; @Component({ selector: 'redaction-dossier-details', @@ -19,8 +20,6 @@ export class DossierDetailsComponent implements OnInit { documentsChartData: DoughnutChartConfig[] = []; owner: User; editingOwner = false; - @Input() filters: { needsWorkFilters: FilterModel[]; statusFilters: FilterModel[] }; - @Output() filtersChanged = new EventEmitter(); @Output() openAssignDossierMembersDialog = new EventEmitter(); @Output() openDossierDictionaryDialog = new EventEmitter(); @Output() toggleCollapse = new EventEmitter(); @@ -29,6 +28,7 @@ export class DossierDetailsComponent implements OnInit { readonly appStateService: AppStateService, readonly translateChartService: TranslateChartService, readonly permissionsService: PermissionsService, + readonly filterService: FilterService, private readonly _changeDetectorRef: ChangeDetectorRef, private readonly _userService: UserService, private readonly _notificationService: NotificationService @@ -76,12 +76,6 @@ export class DossierDetailsComponent implements OnInit { this._changeDetectorRef.detectChanges(); } - toggleFilter(filterType: 'needsWorkFilters' | 'statusFilters', key: string): void { - const filter = this.filters[filterType].find(f => f.key === key); - filter.checked = !filter.checked; - this.filtersChanged.emit(this.filters); - } - async assignOwner(user: User | string) { this.owner = typeof user === 'string' ? this._userService.getRedUserById(user) : user; const dw = Object.assign({}, this.appStateService.activeDossier); diff --git a/apps/red-ui/src/app/modules/dossier/components/dossier-listing-details/dossier-listing-details.component.html b/apps/red-ui/src/app/modules/dossier/components/dossier-listing-details/dossier-listing-details.component.html index 1487103d7..1bb665327 100644 --- a/apps/red-ui/src/app/modules/dossier/components/dossier-listing-details/dossier-listing-details.component.html +++ b/apps/red-ui/src/app/modules/dossier/components/dossier-listing-details/dossier-listing-details.component.html @@ -26,9 +26,8 @@
{ @Input() dossiersChartData: DoughnutChartConfig[]; @Input() documentsChartData: DoughnutChartConfig[]; - @Input() filters: FilterModel[]; - @Output() filtersChanged = new EventEmitter(); - constructor(readonly appStateService: AppStateService) {} - - toggleFilter(key: string): void { - const filter = this.filters.find(f => f.key === key); - filter.checked = !filter.checked; - this.filtersChanged.emit(this.filters); - } + constructor( + readonly appStateService: AppStateService, + readonly filterService: FilterService + ) {} } diff --git a/apps/red-ui/src/app/modules/dossier/components/scroll-button/scroll-button.component.html b/apps/red-ui/src/app/modules/dossier/components/scroll-button/scroll-button.component.html index 0ca366bbc..a0319cacd 100644 --- a/apps/red-ui/src/app/modules/dossier/components/scroll-button/scroll-button.component.html +++ b/apps/red-ui/src/app/modules/dossier/components/scroll-button/scroll-button.component.html @@ -1,14 +1,14 @@
@@ -50,14 +46,14 @@ @@ -65,7 +61,7 @@
- {{ filesCount(dw) }} + {{ dw.files.length }}
@@ -123,7 +119,7 @@
@@ -139,11 +135,9 @@
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 d4aefdd9c..0d8a7354d 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 @@ -1,4 +1,12 @@ -import { Component, Injector, OnDestroy, OnInit, TemplateRef, ViewChild } from '@angular/core'; +import { + ChangeDetectionStrategy, + Component, + Injector, + OnDestroy, + OnInit, + TemplateRef, + ViewChild +} from '@angular/core'; import { Dossier, DossierTemplateModel } from '@redaction/red-ui-http'; import { AppStateService } from '@state/app-state.service'; import { UserService } from '@services/user.service'; @@ -12,7 +20,7 @@ import { filter, tap } from 'rxjs/operators'; import { TranslateChartService } from '@services/translate-chart.service'; import { RedactionFilterSorter } from '@utils/sorters/redaction-filter-sorter'; import { StatusSorter } from '@utils/sorters/status-sorter'; -import { NavigationEnd, NavigationStart, Router } from '@angular/router'; +import { NavigationStart, Router } from '@angular/router'; import { DossiersDialogService } from '../../services/dossiers-dialog.service'; import { OnAttach, OnDetach } from '@utils/custom-route-reuse.strategy'; import { FilterModel } from '@shared/components/filters/popup-filter/model/filter.model'; @@ -20,22 +28,20 @@ import { annotationFilterChecker, dossierMemberChecker, dossierStatusChecker, - dossierTemplateChecker, - processFilters + dossierTemplateChecker } from '@shared/components/filters/popup-filter/utils/filter-utils'; import { UserPreferenceService } from '../../../../services/user-preference.service'; -import { FilterConfig } from '../../../shared/components/page-header/models/filter-config.model'; import { ButtonConfig } from '../../../shared/components/page-header/models/button-config.model'; import { FilterService } from '../../../shared/services/filter.service'; import { SearchService } from '../../../shared/services/search.service'; import { ScreenStateService } from '../../../shared/services/screen-state.service'; import { NewBaseListingComponent } from '../../../shared/base/new-base-listing.component'; -import { FilterWrapper } from '../../../shared/components/filters/popup-filter/model/filter-wrapper.model'; import { ScreenNames, SortingService } from '../../../../services/sorting.service'; @Component({ templateUrl: './dossier-listing-screen.component.html', styleUrls: ['./dossier-listing-screen.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, providers: [FilterService, SearchService, ScreenStateService, SortingService] }) export class DossierListingScreenComponent @@ -44,13 +50,6 @@ export class DossierListingScreenComponent { dossiersChartData: DoughnutChartConfig[] = []; documentsChartData: DoughnutChartConfig[] = []; - statusFilters: FilterModel[]; - peopleFilters: FilterModel[]; - needsWorkFilters: FilterModel[]; - dossierTemplateFilters: FilterModel[]; - detailsContainerFilters: FilterModel[] = []; - quickFilters: FilterModel[]; - filterConfigs: FilterConfig[]; buttonConfigs: ButtonConfig[] = [ { label: this._translateService.instant('dossier-listing.add-new'), @@ -89,67 +88,27 @@ export class DossierListingScreenComponent this._loadEntitiesFromState(); } - get activeDossiersCount(): number { - return this._screenStateService.entities.filter( - p => p.dossier.status === Dossier.StatusEnum.ACTIVE - ).length; - } - - get inactiveDossiersCount(): number { - return this._screenStateService.entities.length - this.activeDossiersCount; - } - - get displayed$(): Observable { - return this._screenStateService.displayedEntities$; - } - - get entities$(): Observable { - return this._screenStateService.entities$; - } - - protected get _filters(): FilterWrapper[] { - return [ - { values: this.statusFilters, checker: dossierStatusChecker }, - { values: this.peopleFilters, checker: dossierMemberChecker }, - { - values: this.needsWorkFilters, - checker: annotationFilterChecker, - matchAll: true, - checkerArgs: this.permissionsService - }, - { values: this.dossierTemplateFilters, checker: dossierTemplateChecker }, - { - values: this.quickFilters, - checker: (dw: DossierWrapper) => - this.quickFilters.reduce((acc, f) => acc || (f.checked && f.checker(dw)), false) - } - ]; - } - ngOnInit(): void { - this._calculateData(); + this.calculateData(); this._dossierAutoUpdateTimer = timer(0, 10000) .pipe( tap(async () => { await this._appStateService.loadAllDossiers(); this._loadEntitiesFromState(); + this.calculateData(); }) ) .subscribe(); this._fileChangedSub = this._appStateService.fileChanged.subscribe(() => { - this._calculateData(); + this.calculateData(); }); this._routerEventsScrollPositionSub = this._router.events - .pipe( - filter( - events => events instanceof NavigationStart || events instanceof NavigationEnd - ) - ) - .subscribe(event => { - if (event instanceof NavigationStart && event.url !== '/main/dossiers') { + .pipe(filter(event => event instanceof NavigationStart)) + .subscribe((event: NavigationStart) => { + if (event.url !== '/main/dossiers') { this._lastScrollPosition = this.scrollViewport.measureScrollOffset('top'); } }); @@ -172,17 +131,12 @@ export class DossierListingScreenComponent this._fileChangedSub.unsubscribe(); } - filesCount(dossier: DossierWrapper) { - return dossier.files.length; - } - getDossierTemplate(dw: DossierWrapper): DossierTemplateModel { return this._appStateService.getDossierTemplateById(dw.dossier.dossierTemplateId); } openAddDossierDialog(): void { this._dialogService.openAddDossierDialog(async addResponse => { - this._calculateData(); await this._router.navigate([`/main/dossiers/${addResponse.dossier.dossierId}`]); if (addResponse.addMembers) { this._dialogService.openDialog('editDossier', null, { @@ -193,16 +147,8 @@ export class DossierListingScreenComponent }); } - actionPerformed() { - this._calculateData(); - } - - filtersChanged(event) { - this._filterService.filtersChanged(event); - } - - protected _preFilter() { - this.detailsContainerFilters = this.statusFilters.map(f => ({ ...f })); + filtersChanged() { + this.filterService.filterEntities(); } private _loadEntitiesFromState() { @@ -213,19 +159,26 @@ export class DossierListingScreenComponent return this._userService.user; } - private _calculateData() { + private get _activeDossiersCount(): number { + return this._screenStateService.entities.filter( + p => p.dossier.status === Dossier.StatusEnum.ACTIVE + ).length; + } + + private get _inactiveDossiersCount(): number { + return this._screenStateService.entities.length - this._activeDossiersCount; + } + + calculateData() { this._computeAllFilters(); - this._filterService.setFilters(this._filters); - this._filterService.setPreFilters(() => this._preFilter()); - this._filterService.filterEntities(); - this.dossiersChartData = [ - { value: this.activeDossiersCount, color: 'ACTIVE', label: 'active' }, - { value: this.inactiveDossiersCount, color: 'DELETED', label: 'archived' } + { value: this._activeDossiersCount, color: 'ACTIVE', label: 'active' }, + { value: this._inactiveDossiersCount, color: 'DELETED', label: 'archived' } ]; const groups = groupBy(this._appStateService.aggregatedFiles, 'status'); this.documentsChartData = []; + for (const key of Object.keys(groups)) { this.documentsChartData.push({ value: groups[key].length, @@ -245,6 +198,7 @@ export class DossierListingScreenComponent const allDistinctPeople = new Set(); const allDistinctNeedsWork = new Set(); const allDistinctDossierTemplates = new Set(); + this._screenStateService?.entities?.forEach(entry => { // all people entry.dossier.memberIds.forEach(f => allDistinctPeople.add(f)); @@ -259,114 +213,105 @@ export class DossierListingScreenComponent if (entry.hasNone) allDistinctNeedsWork.add('none'); }); - // Rule set allDistinctDossierTemplates.add(entry.dossierTemplateId); }); - const statusFilters = []; - allDistinctFileStatus.forEach(status => { - statusFilters.push({ - key: status, - label: this._translateService.instant(status) - }); - }); - statusFilters.sort((a, b) => StatusSorter[a.key] - StatusSorter[b.key]); - this.statusFilters = processFilters(this.statusFilters, statusFilters); + const statusFilters = [...allDistinctFileStatus].map(status => ({ + key: status, + label: this._translateService.instant(status) + })); - const peopleFilters = []; - allDistinctPeople.forEach(userId => { - peopleFilters.push({ - key: userId, - label: this._userService.getNameForId(userId) - }); - }); - this.peopleFilters = processFilters(this.peopleFilters, peopleFilters); - - const needsWorkFilters = []; - allDistinctNeedsWork.forEach(type => { - needsWorkFilters.push({ - key: type, - label: `filter.${type}` - }); + this.filterService.addFilter({ + slug: 'statusFilters', + label: this._translateService.instant('filters.status'), + icon: 'red:status', + values: statusFilters.sort(StatusSorter.byKey), + checker: dossierStatusChecker }); - needsWorkFilters.sort( - (a, b) => RedactionFilterSorter[a.key] - RedactionFilterSorter[b.key] - ); - this.needsWorkFilters = processFilters(this.needsWorkFilters, needsWorkFilters); + const peopleFilters = [...allDistinctPeople].map(userId => ({ + key: userId, + label: this._userService.getNameForId(userId) + })); - const dossierTemplateFilters = []; - allDistinctDossierTemplates.forEach(dossierTemplateId => { - dossierTemplateFilters.push({ - key: dossierTemplateId, - label: this._appStateService.getDossierTemplateById(dossierTemplateId).name - }); + this.filterService.addFilter({ + slug: 'peopleFilters', + label: this._translateService.instant('filters.people'), + icon: 'red:user', + values: peopleFilters, + checker: dossierMemberChecker }); - this.dossierTemplateFilters = processFilters( - this.dossierTemplateFilters, - dossierTemplateFilters - ); - this._createFilterConfigs(); - this._createQuickFilters(); - } + const needsWorkFilters = [...allDistinctNeedsWork].map(type => ({ + key: type, + label: `filter.${type}` + })); - private _createFilterConfigs() { - this.filterConfigs = [ - { - label: this._translateService.instant('filters.status'), - primaryFilters: this.statusFilters, - icon: 'red:status' - }, - { - label: this._translateService.instant('filters.people'), - primaryFilters: this.peopleFilters, - icon: 'red:user' - }, - { - label: this._translateService.instant('filters.needs-work'), - primaryFilters: this.needsWorkFilters, - icon: 'red:needs-work', - filterTemplate: this._needsWorkTemplate - }, - { - label: this._translateService.instant('filters.dossier-templates'), - primaryFilters: this.dossierTemplateFilters, - icon: 'red:template', - hide: this.dossierTemplateFilters.length <= 1 - } - ]; + this.filterService.addFilter({ + slug: 'needsWorkFilters', + label: this._translateService.instant('filters.needs-work'), + icon: 'red:needs-work', + filterTemplate: this._needsWorkTemplate, + values: needsWorkFilters.sort(RedactionFilterSorter.byKey), + checker: annotationFilterChecker, + matchAll: true, + checkerArgs: this.permissionsService + }); + + const dossierTemplateFilters = [...allDistinctDossierTemplates].map(id => ({ + key: id, + label: this._appStateService.getDossierTemplateById(id).name + })); + + this.filterService.addFilter({ + slug: 'dossierTemplateFilters', + label: this._translateService.instant('filters.dossier-templates'), + icon: 'red:template', + hide: this.filterService.getFilter('dossierTemplateFilters')?.values?.length <= 1, + values: dossierTemplateFilters, + checker: dossierTemplateChecker + }); + + const quickFilters = this._createQuickFilters(); + this.filterService.addFilter({ + slug: 'quickFilters', + values: quickFilters, + checker: (dw: DossierWrapper) => + quickFilters.reduce((acc, f) => acc || (f.checked && f.checker(dw)), false) + }); + + this.filterService.filterEntities(); } private _createQuickFilters() { + const myDossiersLabel = this._translateService.instant( + 'dossier-listing.quick-filters.my-dossiers' + ); const filters: FilterModel[] = [ { - key: this._user.id, - label: this._translateService.instant('dossier-listing.quick-filters.my-dossiers'), + key: 'my-dossiers', + label: myDossiersLabel, checker: (dw: DossierWrapper) => dw.ownerId === this._user.id }, { - key: this._user.id, + key: 'to-approve', label: this._translateService.instant('dossier-listing.quick-filters.to-approve'), checker: (dw: DossierWrapper) => dw.approverIds.includes(this._user.id) }, { - key: this._user.id, + key: 'to-review', label: this._translateService.instant('dossier-listing.quick-filters.to-review'), checker: (dw: DossierWrapper) => dw.memberIds.includes(this._user.id) }, { - key: this._user.id, + key: 'other', label: this._translateService.instant('dossier-listing.quick-filters.other'), checker: (dw: DossierWrapper) => !dw.memberIds.includes(this._user.id) } ]; - this.quickFilters = filters.filter( - f => - f.label === - this._translateService.instant('dossier-listing.quick-filters.my-dossiers') || - this._userPreferenceService.areDevFeaturesEnabled + 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.html b/apps/red-ui/src/app/modules/dossier/screens/dossier-overview-screen/dossier-overview-screen.component.html index 726486399..fcf01bb2a 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/dossier-overview-screen/dossier-overview-screen.component.html +++ b/apps/red-ui/src/app/modules/dossier/screens/dossier-overview-screen/dossier-overview-screen.component.html @@ -1,6 +1,5 @@
@@ -49,23 +48,20 @@ {{ 'dossier-overview.table-header.title' - | translate: { length: displayedEntities.length || 0 } + | translate: { length: (displayedEntities$ | async)?.length || 0 } }} - +
@@ -122,14 +118,14 @@ @@ -137,9 +133,10 @@
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 d64614254..1f6d1a578 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 @@ -1,4 +1,5 @@ import { + ChangeDetectorRef, Component, ElementRef, HostListener, @@ -28,13 +29,11 @@ import { RedactionFilterSorter } from '@utils/sorters/redaction-filter-sorter'; import { StatusSorter } from '@utils/sorters/status-sorter'; import { convertFiles, handleFileDrop } from '@utils/file-drop-utils'; import { DossiersDialogService } from '../../services/dossiers-dialog.service'; -import { BaseListingComponent } from '@shared/base/base-listing.component'; import { DossierWrapper } from '@state/model/dossier.wrapper'; import { OnAttach, OnDetach } from '@utils/custom-route-reuse.strategy'; import { annotationFilterChecker, - keyChecker, - processFilters + keyChecker } from '@shared/components/filters/popup-filter/utils/filter-utils'; import { FilterModel } from '@shared/components/filters/popup-filter/model/filter.model'; import { AppConfigKey, AppConfigService } from '../../../app-config/app-config.service'; @@ -44,6 +43,7 @@ import { FilterService } from '../../../shared/services/filter.service'; import { SearchService } from '../../../shared/services/search.service'; import { ScreenStateService } from '../../../shared/services/screen-state.service'; import { ScreenNames, SortingService } from '../../../../services/sorting.service'; +import { NewBaseListingComponent } from '../../../shared/base/new-base-listing.component'; @Component({ templateUrl: './dossier-overview-screen.component.html', @@ -51,25 +51,14 @@ import { ScreenNames, SortingService } from '../../../../services/sorting.servic providers: [FilterService, SearchService, ScreenStateService, SortingService] }) export class DossierOverviewScreenComponent - extends BaseListingComponent + extends NewBaseListingComponent implements OnInit, OnDestroy, OnDetach, OnAttach { - statusFilters: FilterModel[]; - peopleFilters: FilterModel[]; - needsWorkFilters: FilterModel[]; collapsedDetails = false; - detailsContainerFilters: { - needsWorkFilters: FilterModel[]; - statusFilters: FilterModel[]; - } = { needsWorkFilters: [], statusFilters: [] }; readonly itemSize = 80; - quickFilters: FilterModel[]; filterConfigs: FilterConfig[]; actionConfigs: ActionConfig[]; - protected readonly _searchKey = 'searchField'; - protected readonly _selectionKey = 'fileId'; - @ViewChild(DossierDetailsComponent, { static: false }) private readonly _dossierDetailsComponent: DossierDetailsComponent; private _filesAutoUpdateTimer: Subscription; @@ -95,10 +84,13 @@ export class DossierOverviewScreenComponent private readonly _appStateService: AppStateService, private readonly _userPreferenceControllerService: UserPreferenceControllerService, private readonly _appConfigService: AppConfigService, + private readonly _changeDetectorRef: ChangeDetectorRef, protected readonly _injector: Injector ) { super(_injector); this._sortingService.setScreenName(ScreenNames.DOSSIER_OVERVIEW); + this._searchService.setSearchKey('searchField'); + this._screenStateService.setIdKey('fileId'); this._loadEntitiesFromState(); } @@ -111,39 +103,15 @@ export class DossierOverviewScreenComponent } get checkedRequiredFilters() { - return this.quickFilters.filter(f => f.required && f.checked); + return this.filterService + .getFilter('quickFilters') + ?.values.filter(f => f.required && f.checked); } get checkedNotRequiredFilters() { - return this.quickFilters.filter(f => !f.required && f.checked); - } - - protected get _filters(): { - values: FilterModel[]; - checker: Function; - matchAll?: boolean; - checkerArgs?: any; - }[] { - return [ - { values: this.statusFilters, checker: keyChecker('status') }, - { values: this.peopleFilters, checker: keyChecker('currentReviewer') }, - { - values: this.needsWorkFilters, - checker: annotationFilterChecker, - matchAll: true, - checkerArgs: this.permissionsService - }, - { - values: this.quickFilters, - checker: (file: FileStatusWrapper) => - this.checkedRequiredFilters.reduce((acc, f) => acc && f.checker(file), true) && - (this.checkedNotRequiredFilters.length === 0 || - this.checkedNotRequiredFilters.reduce( - (acc, f) => acc || f.checker(file), - false - )) - } - ]; + return this.filterService + .getFilter('quickFilters') + ?.values.filter(f => !f.required && f.checked); } isLastOpenedFile(fileStatus: FileStatusWrapper): boolean { @@ -241,17 +209,18 @@ export class DossierOverviewScreenComponent } calculateData(): void { - if (!this._appStateService.activeDossierId) { - return; - } + if (!this._appStateService.activeDossierId) return; + this._loadEntitiesFromState(); this._computeAllFilters(); - this._filterEntities(); + + this.filterService.filterEntities(); + this._dossierDetailsComponent?.calculateChartConfig(); this._changeDetectorRef.detectChanges(); } - fileId(index, item) { + trackByFileId(index: number, item: FileStatusWrapper) { return item.fileId; } @@ -278,7 +247,7 @@ export class DossierOverviewScreenComponent } bulkActionPerformed() { - this.selectedEntitiesIds = []; + this._screenStateService.selectedEntitiesIds$.next([]); this.reloadDossiers(); } @@ -296,9 +265,7 @@ export class DossierOverviewScreenComponent dossierWrapper: this.activeDossier, section: 'members' }, - () => { - this.reloadDossiers(); - } + () => this.reloadDossiers() ); } @@ -317,15 +284,8 @@ export class DossierOverviewScreenComponent .add(this._appConfigService.getConfig(AppConfigKey.RECENT_PERIOD_IN_HOURS), 'hours') .isAfter(moment()); - protected _preFilter() { - this.detailsContainerFilters = { - needsWorkFilters: this.needsWorkFilters.map(f => ({ ...f })), - statusFilters: this.statusFilters.map(f => ({ ...f })) - }; - } - private _loadEntitiesFromState() { - if (this.activeDossier) this.allEntities = this.activeDossier.files; + if (this.activeDossier) this._screenStateService.setEntities(this.activeDossier.files); } private async _uploadFiles(files: FileUploadModel[]) { @@ -333,7 +293,7 @@ export class DossierOverviewScreenComponent if (fileCount) { this._statusOverlayService.openUploadStatusOverlay(); } - this._changeDetectorRef.detectChanges(); + // this._changeDetectorRef.detectChanges(); } private _computeAllFilters() { @@ -344,7 +304,7 @@ export class DossierOverviewScreenComponent const allDistinctAddedDates = new Set(); const allDistinctNeedsWork = new Set(); - this.allEntities.forEach(file => { + this._screenStateService.entities.forEach(file => { allDistinctPeople.add(file.currentReviewer); allDistinctFileStatusWrapper.add(file.status); allDistinctAddedDates.add(moment(file.added).format('DD/MM/YYYY')); @@ -359,13 +319,18 @@ export class DossierOverviewScreenComponent if (file.hasNone) allDistinctNeedsWork.add('none'); }); - const statusFilters = [...allDistinctFileStatusWrapper].map(item => ({ + const statusFilters = [...allDistinctFileStatusWrapper].map(item => ({ key: item, label: this._translateService.instant(item) })); - statusFilters.sort((a, b) => StatusSorter[a.key] - StatusSorter[b.key]); - this.statusFilters = processFilters(this.statusFilters, statusFilters); + this.filterService.addFilter({ + slug: 'statusFilters', + label: this._translateService.instant('filters.status'), + icon: 'red:status', + values: statusFilters.sort(StatusSorter.byKey), + checker: keyChecker('status') + }); const peopleFilters = []; if (allDistinctPeople.has(undefined) || allDistinctPeople.has(null)) { @@ -382,30 +347,54 @@ export class DossierOverviewScreenComponent label: this._userService.getNameForId(userId) }); }); - this.peopleFilters = processFilters(this.peopleFilters, peopleFilters); + this.filterService.addFilter({ + slug: 'peopleFilters', + label: this._translateService.instant('filters.assigned-people'), + icon: 'red:user', + values: peopleFilters, + checker: keyChecker('currentReviewer') + }); - const needsWorkFilters = [...allDistinctNeedsWork].map(item => ({ + const needsWorkFilters = [...allDistinctNeedsWork].map(item => ({ key: item, label: this._translateService.instant('filter.' + item) })); - needsWorkFilters.sort( - (a, b) => RedactionFilterSorter[a.key] - RedactionFilterSorter[b.key] - ); - this.needsWorkFilters = processFilters(this.needsWorkFilters, needsWorkFilters); - this._createQuickFilters(); - this._createFilterConfigs(); + this.filterService.addFilter({ + slug: 'needsWorkFilters', + label: this._translateService.instant('filters.needs-work'), + icon: 'red:needs-work', + filterTemplate: this._needsWorkTemplate, + values: needsWorkFilters.sort(RedactionFilterSorter.byKey), + checker: annotationFilterChecker, + matchAll: true, + checkerArgs: this.permissionsService + }); + + this.filterService.addFilter({ + slug: 'quickFilters', + values: this._createQuickFilters(), + checker: (file: FileStatusWrapper) => + this.checkedRequiredFilters.reduce((acc, f) => acc && f.checker(file), true) && + (this.checkedNotRequiredFilters.length === 0 || + this.checkedNotRequiredFilters.reduce( + (acc, f) => acc || f.checker(file), + false + )) + }); + this._createActionConfigs(); } private _createQuickFilters() { - if (this.allEntities.filter(this.recentlyModifiedChecker).length > 0) { + let quickFilters = []; + if (this._screenStateService.entities.filter(this.recentlyModifiedChecker).length > 0) { const recentPeriod = this._appConfigService.getConfig( AppConfigKey.RECENT_PERIOD_IN_HOURS ); - this.quickFilters = [ + quickFilters = [ { - key: this.user.id, + key: 'recent', label: this._translateService.instant('dossier-overview.quick-filters.recent', { hours: recentPeriod }), @@ -413,26 +402,24 @@ export class DossierOverviewScreenComponent checker: this.recentlyModifiedChecker } ]; - } else { - this.quickFilters = []; } - this.quickFilters = [ - ...this.quickFilters, + return [ + ...quickFilters, { - key: this.user.id, + key: 'assigned-to-me', label: this._translateService.instant( 'dossier-overview.quick-filters.assigned-to-me' ), checker: (file: FileStatusWrapper) => file.currentReviewer === this.user.id }, { - key: this.user.id, + key: 'unassigned', label: this._translateService.instant('dossier-overview.quick-filters.unassigned'), checker: (file: FileStatusWrapper) => !file.currentReviewer }, { - key: this.user.id, + key: 'assigned-to-others', label: this._translateService.instant( 'dossier-overview.quick-filters.assigned-to-others' ), @@ -442,27 +429,6 @@ export class DossierOverviewScreenComponent ]; } - private _createFilterConfigs() { - this.filterConfigs = [ - { - label: this._translateService.instant('filters.status'), - primaryFilters: this.statusFilters, - icon: 'red:status' - }, - { - label: this._translateService.instant('filters.assigned-people'), - primaryFilters: this.peopleFilters, - icon: 'red:user' - }, - { - label: this._translateService.instant('filters.needs-work'), - primaryFilters: this.needsWorkFilters, - icon: 'red:needs-work', - filterTemplate: this._needsWorkTemplate - } - ]; - } - private _createActionConfigs() { this.actionConfigs = [ { diff --git a/apps/red-ui/src/app/modules/dossier/services/dossiers.service.ts b/apps/red-ui/src/app/modules/dossier/services/dossiers.service.ts new file mode 100644 index 000000000..554967f65 --- /dev/null +++ b/apps/red-ui/src/app/modules/dossier/services/dossiers.service.ts @@ -0,0 +1,21 @@ +import { Injectable } from '@angular/core'; +import { DossierControllerService } from '@redaction/red-ui-http'; + +@Injectable() +export class DossiersService { + constructor(private readonly _dossierControllerService: DossierControllerService) {} + + getDeletedDossiers() { + return this._dossierControllerService.getDeletedDossiers().toPromise(); + } + + restore(dossierIds: string | Array): Promise { + if (typeof dossierIds === 'string') dossierIds = [dossierIds]; + return this._dossierControllerService.restoreDossiers(dossierIds).toPromise(); + } + + hardDelete(dossierIds: string | Array): Promise { + if (typeof dossierIds === 'string') dossierIds = [dossierIds]; + return this._dossierControllerService.hardDeleteDossiers(dossierIds).toPromise(); + } +} diff --git a/apps/red-ui/src/app/modules/shared/base/base-listing.component.ts b/apps/red-ui/src/app/modules/shared/base/base-listing.component.ts index 1aac87bc6..4592e307b 100644 --- a/apps/red-ui/src/app/modules/shared/base/base-listing.component.ts +++ b/apps/red-ui/src/app/modules/shared/base/base-listing.component.ts @@ -9,6 +9,7 @@ import { SearchService } from '../services/search.service'; import { ScreenStateService } from '../services/screen-state.service'; import { getFilteredEntities } from '../components/filters/popup-filter/utils/filter-utils'; import { debounce } from '../../../utils/debounce'; +import { FilterWrapper } from '../components/filters/popup-filter/model/filter-wrapper.model'; // Functionalities: Filter, search, select, sort @@ -37,7 +38,7 @@ export abstract class BaseListingComponent { protected readonly _selectionKey: string; // Overwrite this in ngOnInit @ViewChild(QuickFiltersComponent) - protected _quickFilters: QuickFiltersComponent; + protected _quickFilters: QuickFiltersComponent; private _searchValue = ''; @@ -65,12 +66,7 @@ export abstract class BaseListingComponent { return this._sortingService.getSortingOption(); } - protected get _filters(): { - values: FilterModel[]; - checker: Function; - matchAll?: boolean; - checkerArgs?: any; - }[] { + protected get _filters(): FilterWrapper[] { return []; } @@ -103,7 +99,6 @@ export abstract class BaseListingComponent { } resetFilters() { - this._quickFilters.deactivateFilters(); this.showResetFilters = false; this.filtersChanged(); } diff --git a/apps/red-ui/src/app/modules/shared/base/new-base-listing.component.ts b/apps/red-ui/src/app/modules/shared/base/new-base-listing.component.ts index 62af13e62..2c2ac8e6c 100644 --- a/apps/red-ui/src/app/modules/shared/base/new-base-listing.component.ts +++ b/apps/red-ui/src/app/modules/shared/base/new-base-listing.component.ts @@ -1,47 +1,80 @@ import { Component, Injector, ViewChild } from '@angular/core'; -import { ScreenName, SortingOption, SortingService } from '@services/sorting.service'; -import { FilterModel } from '../components/filters/popup-filter/model/filter.model'; -import { QuickFiltersComponent } from '../components/filters/quick-filters/quick-filters.component'; +import { SortingOption, SortingService } from '@services/sorting.service'; import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling'; import { FilterService } from '../services/filter.service'; import { SearchService } from '../services/search.service'; import { ScreenStateService } from '../services/screen-state.service'; import { FilterWrapper } from '../components/filters/popup-filter/model/filter-wrapper.model'; +import { Observable } from 'rxjs'; +import { FilterModel } from '../components/filters/popup-filter/model/filter.model'; @Component({ template: '' }) -export abstract class NewBaseListingComponent { - @ViewChild(CdkVirtualScrollViewport) scrollViewport: CdkVirtualScrollViewport; +export abstract class NewBaseListingComponent { + @ViewChild(CdkVirtualScrollViewport) + readonly scrollViewport: CdkVirtualScrollViewport; + readonly filterService: FilterService; protected readonly _sortingService: SortingService; - protected readonly _filterService: FilterService; protected readonly _searchService: SearchService; protected readonly _screenStateService: ScreenStateService; - // Overwrite this in ngOnInit - @ViewChild(QuickFiltersComponent) - protected _quickFilters: QuickFiltersComponent; - protected constructor(protected readonly _injector: Injector) { + this.filterService = this._injector.get>(FilterService); this._sortingService = this._injector.get(SortingService); - this._filterService = this._injector.get>(FilterService); this._searchService = this._injector.get>(SearchService); this._screenStateService = this._injector.get>(ScreenStateService); } + get selectedEntitiesIds$(): Observable { + return this._screenStateService.selectedEntitiesIds$; + } + + get displayedEntities$(): Observable { + return this._screenStateService.displayedEntities$; + } + + get allEntities$(): Observable { + return this._screenStateService.entities$; + } + + get areAllEntitiesSelected() { + return this._screenStateService.areAllEntitiesSelected; + } + + get areSomeEntitiesSelected() { + return this._screenStateService.areSomeEntitiesSelected; + } + get sortingOption(): SortingOption { return this._sortingService.getSortingOption(); } + getFilter$(slug: string): Observable { + return this.filterService.getFilter$(slug); + } + protected get _filters(): FilterWrapper[] { return []; } resetFilters() { - this._quickFilters.deactivateFilters(); - this._filterService.reset(); + this.filterService.reset(); } toggleSort($event) { this._sortingService.toggleSort($event); } + + toggleSelectAll() { + return this._screenStateService.toggleSelectAll(); + } + + toggleEntitySelected(event: MouseEvent, entity: T) { + event.stopPropagation(); + return this._screenStateService.toggleEntitySelected(entity); + } + + isSelected(entity: T) { + return this._screenStateService.isSelected(entity); + } } diff --git a/apps/red-ui/src/app/modules/shared/components/filters/popup-filter/model/filter-wrapper.model.ts b/apps/red-ui/src/app/modules/shared/components/filters/popup-filter/model/filter-wrapper.model.ts index 9e1a890c5..1b19edc1d 100644 --- a/apps/red-ui/src/app/modules/shared/components/filters/popup-filter/model/filter-wrapper.model.ts +++ b/apps/red-ui/src/app/modules/shared/components/filters/popup-filter/model/filter-wrapper.model.ts @@ -1,6 +1,12 @@ import { FilterModel } from './filter.model'; +import { TemplateRef } from '@angular/core'; export interface FilterWrapper { + slug: string; + label?: string; + icon?: string; + filterTemplate?: TemplateRef; + hide?: boolean; values: FilterModel[]; checker: Function; matchAll?: boolean; diff --git a/apps/red-ui/src/app/modules/shared/components/filters/popup-filter/popup-filter.component.ts b/apps/red-ui/src/app/modules/shared/components/filters/popup-filter/popup-filter.component.ts index 0064a4f40..7cca79894 100644 --- a/apps/red-ui/src/app/modules/shared/components/filters/popup-filter/popup-filter.component.ts +++ b/apps/red-ui/src/app/modules/shared/components/filters/popup-filter/popup-filter.component.ts @@ -1,5 +1,4 @@ import { - ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, @@ -17,7 +16,6 @@ import { TranslateService } from '@ngx-translate/core'; selector: 'redaction-popup-filter', templateUrl: './popup-filter.component.html', styleUrls: ['./popup-filter.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, providers: [ { provide: MAT_CHECKBOX_DEFAULT_OPTIONS, diff --git a/apps/red-ui/src/app/modules/shared/components/filters/quick-filters/quick-filters.component.html b/apps/red-ui/src/app/modules/shared/components/filters/quick-filters/quick-filters.component.html index 964328673..59f0d5177 100644 --- a/apps/red-ui/src/app/modules/shared/components/filters/quick-filters/quick-filters.component.html +++ b/apps/red-ui/src/app/modules/shared/components/filters/quick-filters/quick-filters.component.html @@ -1,6 +1,6 @@
diff --git a/apps/red-ui/src/app/modules/shared/components/filters/quick-filters/quick-filters.component.ts b/apps/red-ui/src/app/modules/shared/components/filters/quick-filters/quick-filters.component.ts index fcb682f08..15fb86b2f 100644 --- a/apps/red-ui/src/app/modules/shared/components/filters/quick-filters/quick-filters.component.ts +++ b/apps/red-ui/src/app/modules/shared/components/filters/quick-filters/quick-filters.component.ts @@ -1,25 +1,14 @@ import { Component, EventEmitter, Input, Output } from '@angular/core'; import { FilterModel } from '../popup-filter/model/filter.model'; +import { FilterService } from '@shared/services/filter.service'; @Component({ selector: 'redaction-quick-filters', templateUrl: './quick-filters.component.html', styleUrls: ['./quick-filters.component.scss'] }) -export class QuickFiltersComponent { +export class QuickFiltersComponent { @Output() filtersChanged = new EventEmitter(); - @Input() filters: FilterModel[]; - get hasActiveFilters(): boolean { - return this.filters.filter(f => f.checked).length > 0; - } - - deactivateFilters() { - for (const filter of this.filters) filter.checked = false; - } - - toggle(filter: FilterModel) { - filter.checked = !filter.checked; - this.filtersChanged.emit(this.filters); - } + constructor(readonly filterService: FilterService) {} } diff --git a/apps/red-ui/src/app/modules/shared/components/full-page-loading-indicator/full-page-loading-indicator.component.ts b/apps/red-ui/src/app/modules/shared/components/full-page-loading-indicator/full-page-loading-indicator.component.ts index 6cdd92f19..d03e2ea20 100644 --- a/apps/red-ui/src/app/modules/shared/components/full-page-loading-indicator/full-page-loading-indicator.component.ts +++ b/apps/red-ui/src/app/modules/shared/components/full-page-loading-indicator/full-page-loading-indicator.component.ts @@ -1,9 +1,10 @@ -import { Component, Input } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; @Component({ selector: 'redaction-full-page-loading-indicator', templateUrl: './full-page-loading-indicator.component.html', - styleUrls: ['./full-page-loading-indicator.component.scss'] + styleUrls: ['./full-page-loading-indicator.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush }) export class FullPageLoadingIndicatorComponent { @Input() displayed = false; diff --git a/apps/red-ui/src/app/modules/shared/components/page-header/models/filter-config.model.ts b/apps/red-ui/src/app/modules/shared/components/page-header/models/filter-config.model.ts index 29b749703..a4474cde2 100644 --- a/apps/red-ui/src/app/modules/shared/components/page-header/models/filter-config.model.ts +++ b/apps/red-ui/src/app/modules/shared/components/page-header/models/filter-config.model.ts @@ -3,6 +3,7 @@ import { TemplateRef } from '@angular/core'; import { BaseHeaderConfig } from './base-config.model'; export interface FilterConfig extends BaseHeaderConfig { - primaryFilters: FilterModel[]; + primaryFilters?: FilterModel[]; + primaryFiltersLabel?: string; filterTemplate?: TemplateRef; } 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 3e960cc89..4e2d0ac7d 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,16 +1,16 @@