diff --git a/apps/red-ui/src/app/modules/admin/admin.module.ts b/apps/red-ui/src/app/modules/admin/admin.module.ts index 2c12acb71..3f31c7f61 100644 --- a/apps/red-ui/src/app/modules/admin/admin.module.ts +++ b/apps/red-ui/src/app/modules/admin/admin.module.ts @@ -32,6 +32,7 @@ import { AddEditUserDialogComponent } from './dialogs/add-edit-user-dialog/add-e import { UsersStatsComponent } from './components/users-stats/users-stats.component'; import { ConfirmDeleteUsersDialogComponent } from './dialogs/confirm-delete-users-dialog/confirm-delete-users-dialog.component'; import { FileAttributesCsvImportDialogComponent } from './dialogs/file-attributes-csv-import-dialog/file-attributes-csv-import-dialog.component'; +import { ActiveFieldsListingComponent } from './dialogs/file-attributes-csv-import-dialog/active-fields-listing/active-fields-listing.component'; const dialogs = [ AddEditRuleSetDialogComponent, @@ -41,7 +42,8 @@ const dialogs = [ EditColorDialogComponent, SmtpAuthDialogComponent, AddEditUserDialogComponent, - ConfirmDeleteUsersDialogComponent + ConfirmDeleteUsersDialogComponent, + FileAttributesCsvImportDialogComponent ]; const screens = [ @@ -66,12 +68,14 @@ const components = [ ComboChartComponent, ComboSeriesVerticalComponent, UsersStatsComponent, + ActiveFieldsListingComponent, + ...dialogs, ...screens ]; @NgModule({ - declarations: [...components, FileAttributesCsvImportDialogComponent], + declarations: [...components], providers: [AdminDialogService], imports: [CommonModule, SharedModule, AdminRoutingModule, AceEditorModule, NgxChartsModule, ColorPickerModule] }) diff --git a/apps/red-ui/src/app/modules/admin/dialogs/file-attributes-csv-import-dialog/active-fields-listing/active-fields-listing.component.html b/apps/red-ui/src/app/modules/admin/dialogs/file-attributes-csv-import-dialog/active-fields-listing/active-fields-listing.component.html new file mode 100644 index 000000000..a33c01a53 --- /dev/null +++ b/apps/red-ui/src/app/modules/admin/dialogs/file-attributes-csv-import-dialog/active-fields-listing/active-fields-listing.component.html @@ -0,0 +1,175 @@ +
+
+
+ + +
+ + {{ 'file-attributes-csv-import.table-header.title' | translate: { length: allEntities.length } }} + + + + + + + + + + +
+ + + + + + + + + + + + + + + + +
+
+ +
+
+ + + + + + + + + +
+
+
+ + + + + +
+
+
+ +
+
+
+ {{ field.name }} +
+
+
+ +
+
+ + + + + + + + +
+
+
+ + + + {{ 'file-attributes-csv-import.types.' + type | translate }} + + + +
+
+
+ +
+
+
+
+ + +
+
+
+
+
diff --git a/apps/red-ui/src/app/modules/admin/dialogs/file-attributes-csv-import-dialog/active-fields-listing/active-fields-listing.component.scss b/apps/red-ui/src/app/modules/admin/dialogs/file-attributes-csv-import-dialog/active-fields-listing/active-fields-listing.component.scss new file mode 100644 index 000000000..42b2d5706 --- /dev/null +++ b/apps/red-ui/src/app/modules/admin/dialogs/file-attributes-csv-import-dialog/active-fields-listing/active-fields-listing.component.scss @@ -0,0 +1,84 @@ +@import '../../../../../../assets/styles/red-variables'; + +redaction-table-col-name::ng-deep { + > div { + padding: 0 13px 0 10px !important; + + &.name { + padding-left: 22px !important; + } + } +} + +.header-item { + padding: 0 24px 0 10px; + box-shadow: none; + border-top: 1px solid $separator; + + .all-caps-label { + margin-right: 10px; + } + + redaction-circle-button { + margin-right: 2px; + } + + .separator { + margin-left: 14px; + background-color: $separator; + width: 1px; + height: 30px; + margin-right: 16px; + } +} + +cdk-virtual-scroll-viewport { + height: calc(100% - 80px); + + ::ng-deep.cdk-virtual-scroll-content-wrapper { + grid-template-columns: 30px minmax(0, 25vw) 150px auto auto auto 11px; + + .table-item { + > div { + height: 50px; + + &:not(.scrollbar-placeholder) { + padding-left: 10px; + + &.center { + align-items: center; + } + } + + &.name { + flex-direction: row; + align-items: center; + justify-content: flex-start; + + &:not(.editing) { + padding-left: 22px; + } + + .edit-name-button { + display: none; + } + + redaction-circle-button:first-of-type { + margin-left: 7px; + margin-right: 2px; + } + } + } + + &:hover .name .edit-name-button { + display: block; + } + } + } + + &.has-scrollbar:hover { + ::ng-deep.cdk-virtual-scroll-content-wrapper { + grid-template-columns: 30px minmax(0, 25vw) 150px auto auto auto; + } + } +} 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 new file mode 100644 index 000000000..84c01afd9 --- /dev/null +++ b/apps/red-ui/src/app/modules/admin/dialogs/file-attributes-csv-import-dialog/active-fields-listing/active-fields-listing.component.ts @@ -0,0 +1,40 @@ +import { Component, EventEmitter, Injector, Input, OnChanges, Output, SimpleChanges } from '@angular/core'; +import { BaseListingComponent } from '../../../../shared/base/base-listing.component'; +import { Field } from '../file-attributes-csv-import-dialog.component'; + +@Component({ + selector: 'redaction-active-fields-listing', + templateUrl: './active-fields-listing.component.html', + styleUrls: ['./active-fields-listing.component.scss'] +}) +export class ActiveFieldsListingComponent extends BaseListingComponent implements OnChanges { + @Input() public allEntities: Field[]; + @Output() public allEntitiesChange = new EventEmitter(); + @Output() public setHoveredColumn = new EventEmitter(); + @Output() public toggleFieldActive = new EventEmitter(); + + protected readonly _selectionKey = 'csvColumn'; + + constructor(protected readonly _injector: Injector) { + super(_injector); + } + + ngOnChanges(changes: SimpleChanges): void { + if (changes.allEntities) { + this.displayedEntities = this.allEntities; + this._updateSelection(); + } + } + + public deactivateSelection() { + this.allEntities = [...this.allEntities.filter((field) => !this.isEntitySelected(field))]; + this.allEntitiesChange.emit(this.allEntities); + this.selectedEntitiesIds = []; + } + + public setAttributeForSelection(attribute: string, value: any) { + for (const csvColumn of this.selectedEntitiesIds) { + this.allEntities.find((f) => f.csvColumn === csvColumn)[attribute] = value; + } + } +} diff --git a/apps/red-ui/src/app/modules/admin/dialogs/file-attributes-csv-import-dialog/file-attributes-csv-import-dialog.component.html b/apps/red-ui/src/app/modules/admin/dialogs/file-attributes-csv-import-dialog/file-attributes-csv-import-dialog.component.html index 443b5c7c3..2131fdde6 100644 --- a/apps/red-ui/src/app/modules/admin/dialogs/file-attributes-csv-import-dialog/file-attributes-csv-import-dialog.component.html +++ b/apps/red-ui/src/app/modules/admin/dialogs/file-attributes-csv-import-dialog/file-attributes-csv-import-dialog.component.html @@ -90,7 +90,7 @@
-
-
-
- - -
- - {{ 'file-attributes-csv-import.table-header.title' | translate: { length: activeFields.length } }} - - - - - - - - - - -
- - - - - - - - - - - - - - - - -
-
- -
-
- - - - - - - - - -
-
-
- - - - - -
-
-
- -
-
-
- {{ field.name }} -
-
-
- -
-
- - - - - - - - -
-
-
- - - - {{ 'file-attributes-csv-import.types.' + type | translate }} - - - -
-
-
- -
-
-
-
- - -
-
-
-
-
+
diff --git a/apps/red-ui/src/app/modules/admin/dialogs/file-attributes-csv-import-dialog/file-attributes-csv-import-dialog.component.scss b/apps/red-ui/src/app/modules/admin/dialogs/file-attributes-csv-import-dialog/file-attributes-csv-import-dialog.component.scss index fdf45c402..88d6fc0d2 100644 --- a/apps/red-ui/src/app/modules/admin/dialogs/file-attributes-csv-import-dialog/file-attributes-csv-import-dialog.component.scss +++ b/apps/red-ui/src/app/modules/admin/dialogs/file-attributes-csv-import-dialog/file-attributes-csv-import-dialog.component.scss @@ -114,6 +114,7 @@ > .left { width: 375px; + min-width: 375px; background: $grey-2; .csv-header-pill-content { @@ -198,88 +199,5 @@ > .content-container { width: 100%; - - redaction-table-col-name::ng-deep { - > div { - padding: 0 13px 0 10px !important; - - &.name { - padding-left: 22px !important; - } - } - } - - .header-item { - padding: 0 24px 0 10px; - box-shadow: none; - border-top: 1px solid $separator; - - .all-caps-label { - margin-right: 10px; - } - - redaction-circle-button { - margin-right: 2px; - } - - .separator { - margin-left: 14px; - background-color: $separator; - width: 1px; - height: 30px; - margin-right: 16px; - } - } - - cdk-virtual-scroll-viewport { - height: calc(100% - 80px); - - ::ng-deep.cdk-virtual-scroll-content-wrapper { - grid-template-columns: 30px minmax(0, 25vw) 150px auto auto auto 11px; - - .table-item { - > div { - height: 50px; - - &:not(.scrollbar-placeholder) { - padding-left: 10px; - - &.center { - align-items: center; - } - } - - &.name { - flex-direction: row; - align-items: center; - justify-content: flex-start; - - &:not(.editing) { - padding-left: 22px; - } - - .edit-name-button { - display: none; - } - - redaction-circle-button:first-of-type { - margin-left: 7px; - margin-right: 2px; - } - } - } - - &:hover .name .edit-name-button { - display: block; - } - } - } - - &.has-scrollbar:hover { - ::ng-deep.cdk-virtual-scroll-content-wrapper { - grid-template-columns: 30px minmax(0, 25vw) 150px auto auto auto; - } - } - } } } 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 45a7afd1c..64fa985b3 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 @@ -1,13 +1,13 @@ -import { Component, Inject, ViewChild } from '@angular/core'; -import { AbstractControl, FormBuilder, FormGroup, ValidatorFn, Validators } from '@angular/forms'; +import { Component, Inject, Injector, ViewChild } from '@angular/core'; +import { AbstractControl, FormGroup, ValidatorFn, Validators } from '@angular/forms'; import { AppStateService } from '../../../../state/app-state.service'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import * as Papa from 'papaparse'; import { FileAttributesControllerService } from '@redaction/red-ui-http'; import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling'; -import { debounce } from '../../../../utils/debounce'; import { Observable } from 'rxjs'; import { map, startWith } from 'rxjs/operators'; +import { BaseListingComponent } from '../../../shared/base/base-listing.component'; enum FieldType { Text = 'Text', @@ -15,7 +15,7 @@ enum FieldType { Date = 'Date' } -interface Field { +export interface Field { csvColumn: string; name: string; type: FieldType; @@ -30,17 +30,16 @@ interface Field { templateUrl: './file-attributes-csv-import-dialog.component.html', styleUrls: ['./file-attributes-csv-import-dialog.component.scss'] }) -export class FileAttributesCsvImportDialogComponent { +export class FileAttributesCsvImportDialogComponent extends BaseListingComponent { + protected readonly _searchKey = 'csvColumn'; + public csvFile: File; public ruleSetId: string; public parseResult: { data: any[]; errors: any[]; meta: any; fields: Field[] }; public hoveredColumn: string; public activeFields: Field[] = []; - public selectedFields: string[] = []; public baseConfigForm: FormGroup; public isSearchOpen = false; - public searchForm: FormGroup; - public filteredFields: Field[]; public previewExpanded = true; public filteredKeyOptions: Observable; public keepPreview = false; @@ -50,18 +49,15 @@ export class FileAttributesCsvImportDialogComponent { constructor( private readonly _appStateService: AppStateService, - private readonly _formBuilder: FormBuilder, private readonly _fileAttributesControllerService: FileAttributesControllerService, public dialogRef: MatDialogRef, + protected readonly _injector: Injector, @Inject(MAT_DIALOG_DATA) public data: { csv: File; ruleSetId: string } ) { + super(_injector); this.csvFile = data.csv; this.ruleSetId = data.ruleSetId; - this.searchForm = this._formBuilder.group({ - query: [''] - }); - this.baseConfigForm = this._formBuilder.group({ filenameMappingColumnHeaderName: ['', [Validators.required, this._autocompleteStringValidator()]], delimiter: [undefined, Validators.required], @@ -69,13 +65,6 @@ export class FileAttributesCsvImportDialogComponent { }); this._readFile(); - - this.searchForm.valueChanges.subscribe((value) => this._executeSearch(value)); - } - - @debounce(200) - private _executeSearch(value: { query: string }) { - this.filteredFields = this.parseResult.fields.filter((f) => f.csvColumn.toLowerCase().includes(value.query.toLowerCase())); } private _autocompleteStringValidator(): ValidatorFn { @@ -98,12 +87,14 @@ export class FileAttributesCsvImportDialogComponent { if (!this.baseConfigForm.get('delimiter').value) { this.baseConfigForm.patchValue({ delimiter: this.parseResult.meta.delimiter }); } - this.parseResult.fields = this.parseResult.meta.fields.map((field) => this._buildAttribute(field)); - this.filteredFields = [...this.parseResult.fields]; + this.allEntities = this.parseResult.meta.fields.map((field) => this._buildAttribute(field)); + this.displayedEntities = [...this.allEntities]; this.filteredKeyOptions = this.baseConfigForm.get('filenameMappingColumnHeaderName').valueChanges.pipe( startWith(''), - map((value: string) => this.parseResult.meta.fields.filter((field) => field.toLowerCase().indexOf(value.toLowerCase()) !== -1)) + map((value: string) => + this.allEntities.filter((field) => field.csvColumn.toLowerCase().indexOf(value.toLowerCase()) !== -1).map((field) => field.csvColumn) + ) ); }); reader.readAsText(this.csvFile, this.baseConfigForm.get('encoding').value); @@ -137,9 +128,6 @@ export class FileAttributesCsvImportDialogComponent { } else { this.activeFields.splice(this.activeFields.indexOf(field), 1); this.activeFields = [...this.activeFields]; - if (this.isFieldSelected(field.csvColumn)) { - this.toggleFieldSelected(field.csvColumn); - } } } @@ -157,52 +145,11 @@ export class FileAttributesCsvImportDialogComponent { } public activateAll() { - this.activeFields = [...this.parseResult.fields]; + this.activeFields = [...this.allEntities]; } public deactivateAll() { this.activeFields = []; - this.selectedFields = []; - } - - public toggleFieldSelected(field: string) { - const idx = this.selectedFields.indexOf(field); - if (idx === -1) { - this.selectedFields.push(field); - } else { - this.selectedFields.splice(idx, 1); - } - } - - public toggleSelectAll() { - if (this.areSomeFieldsSelected) { - this.selectedFields = []; - } else { - this.selectedFields = this.activeFields.map((field) => field.csvColumn); - } - } - - public get areAllFieldsSelected() { - return this.activeFields.length !== 0 && this.selectedFields.length === this.activeFields.length; - } - - public get areSomeFieldsSelected() { - return this.selectedFields.length > 0; - } - - public isFieldSelected(field: string) { - return this.selectedFields.indexOf(field) !== -1; - } - - public deactivateSelection() { - this.activeFields = [...this.activeFields.filter((field) => !this.isFieldSelected(field.csvColumn))]; - this.selectedFields = []; - } - - public setAttributeForSelection(attribute: string, value: any) { - for (const csvColumn of this.selectedFields) { - this.activeFields.find((f) => f.csvColumn === csvColumn)[attribute] = value; - } } public async save() { diff --git a/apps/red-ui/src/app/modules/admin/screens/default-colors/default-colors-screen.component.html b/apps/red-ui/src/app/modules/admin/screens/default-colors/default-colors-screen.component.html index 95b6c0fa5..a91352feb 100644 --- a/apps/red-ui/src/app/modules/admin/screens/default-colors/default-colors-screen.component.html +++ b/apps/red-ui/src/app/modules/admin/screens/default-colors/default-colors-screen.component.html @@ -15,7 +15,7 @@
- {{ 'default-colors-screen.table-header.title' | translate: { length: colors.length } }} + {{ 'default-colors-screen.table-header.title' | translate: { length: allEntities.length } }}
@@ -36,7 +36,7 @@ -
+
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 e4f1f461e..b34fa75cf 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 @@ -1,41 +1,35 @@ -import { Component } from '@angular/core'; +import { Component, Injector } from '@angular/core'; import { AppStateService } from '../../../../state/app-state.service'; import { Colors, DictionaryControllerService } from '@redaction/red-ui-http'; import { ActivatedRoute } from '@angular/router'; -import { SortingOption, SortingService } from '../../../../services/sorting.service'; import { PermissionsService } from '../../../../services/permissions.service'; import { AdminDialogService } from '../../services/admin-dialog.service'; +import { BaseListingComponent } from '../../../shared/base/base-listing.component'; @Component({ selector: 'redaction-default-colors-screen', templateUrl: './default-colors-screen.component.html', styleUrls: ['./default-colors-screen.component.scss'] }) -export class DefaultColorsScreenComponent { +export class DefaultColorsScreenComponent extends BaseListingComponent<{ key: string; value: string }> { + protected readonly _sortKey = 'default-colors'; + public viewReady = false; private _colorsObj: Colors; - public colors: { key: string; value: string }[] = []; constructor( private readonly _appStateService: AppStateService, private readonly _activatedRoute: ActivatedRoute, private readonly _dictionaryControllerService: DictionaryControllerService, - private readonly _sortingService: SortingService, private readonly _dialogService: AdminDialogService, - public readonly permissionsService: PermissionsService + public readonly permissionsService: PermissionsService, + protected readonly _injector: Injector ) { + super(_injector); this._appStateService.activateRuleSet(_activatedRoute.snapshot.params.ruleSetId); this._loadColors(); } - public get sortingOption(): SortingOption { - return this._sortingService.getSortingOption('default-colors'); - } - - public toggleSort($event) { - this._sortingService.toggleSort('default-colors', $event); - } - public async loadRuleSetsData(): Promise { await this._appStateService.loadAllRuleSets(); } @@ -46,7 +40,7 @@ export class DefaultColorsScreenComponent { .toPromise() .then((data) => { this._colorsObj = data; - this.colors = Object.keys(data).map((key) => ({ + this.allEntities = Object.keys(data).map((key) => ({ key, value: data[key] })); diff --git a/apps/red-ui/src/app/modules/admin/screens/dictionary-listing/dictionary-listing-screen.component.html b/apps/red-ui/src/app/modules/admin/screens/dictionary-listing/dictionary-listing-screen.component.html index f99b01c39..ab3fd4b2e 100644 --- a/apps/red-ui/src/app/modules/admin/screens/dictionary-listing/dictionary-listing-screen.component.html +++ b/apps/red-ui/src/app/modules/admin/screens/dictionary-listing/dictionary-listing-screen.component.html @@ -17,13 +17,13 @@
- + - {{ 'dictionary-listing.table-header.title' | translate: { length: displayedDictionaries.length } }} + {{ 'dictionary-listing.table-header.title' | translate: { length: displayedEntities.length } }}
@@ -48,7 +48,7 @@
-
+
- +
-
-
- +
+
+
@@ -137,7 +133,7 @@
+ + 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 cf54c6347..e9b951c22 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 @@ -1,79 +1,66 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, Injector, OnInit } from '@angular/core'; import { DoughnutChartConfig } from '../../../shared/components/simple-doughnut-chart/simple-doughnut-chart.component'; import { DictionaryControllerService, TypeValue } from '@redaction/red-ui-http'; -import { SortingOption, SortingService } from '../../../../services/sorting.service'; import { AppStateService } from '../../../../state/app-state.service'; -import { tap } from 'rxjs/operators'; +import { defaultIfEmpty, tap } from 'rxjs/operators'; import { forkJoin } from 'rxjs'; import { PermissionsService } from '../../../../services/permissions.service'; -import { FormBuilder, FormGroup } from '@angular/forms'; -import { debounce } from '../../../../utils/debounce'; import { ActivatedRoute } from '@angular/router'; import { AdminDialogService } from '../../services/admin-dialog.service'; +import { BaseListingComponent } from '../../../shared/base/base-listing.component'; @Component({ selector: 'redaction-dictionary-listing-screen', templateUrl: './dictionary-listing-screen.component.html', styleUrls: ['./dictionary-listing-screen.component.scss'] }) -export class DictionaryListingScreenComponent implements OnInit { +export class DictionaryListingScreenComponent extends BaseListingComponent implements OnInit { + protected readonly _searchKey = 'label'; + protected readonly _selectionKey = 'type'; + protected readonly _sortKey = 'dictionary-listing'; + + public viewReady = false; public chartData: DoughnutChartConfig[] = []; - public dictionaries: TypeValue[]; - public displayedDictionaries: TypeValue[]; - public selectedDictKeys: string[] = []; - public searchForm: FormGroup; constructor( private readonly _dialogService: AdminDialogService, - private readonly _sortingService: SortingService, - private readonly _formBuilder: FormBuilder, private readonly _dictionaryControllerService: DictionaryControllerService, private readonly _activatedRoute: ActivatedRoute, private readonly _appStateService: AppStateService, - public readonly permissionsService: PermissionsService + public readonly permissionsService: PermissionsService, + protected readonly _injector: Injector ) { + super(_injector); this._appStateService.activateRuleSet(_activatedRoute.snapshot.params.ruleSetId); - - this.searchForm = this._formBuilder.group({ - query: [''] - }); - - this.searchForm.valueChanges.subscribe((value) => this._executeSearch(value)); } ngOnInit(): void { this._loadDictionaryData(); - this._calculateData(); - } - - @debounce(200) - private _executeSearch(value: { query: string }) { - this.displayedDictionaries = this.dictionaries.filter((dict) => dict.label.toLowerCase().includes(value.query.toLowerCase())); } private _loadDictionaryData() { const appStateDictionaryData = this._appStateService.dictionaryData[this._appStateService.activeRuleSetId]; - this.dictionaries = Object.keys(appStateDictionaryData) + this.allEntities = Object.keys(appStateDictionaryData) .map((key) => appStateDictionaryData[key]) .filter((d) => !d.virtual || d.type === 'false_positive'); - this.displayedDictionaries = [...this.dictionaries]; - const dataObs = []; - this.dictionaries.forEach((item) => { - const observable = this._dictionaryControllerService.getDictionaryForType(item.type, this._appStateService.activeRuleSetId).pipe( + this.displayedEntities = [...this.allEntities]; + const dataObs = this.allEntities.map((dict) => + this._dictionaryControllerService.getDictionaryForType(dict.type, this._appStateService.activeRuleSetId).pipe( tap((values) => { - item.entries = values.entries ? values.entries : []; + dict.entries = values.entries ? values.entries : []; }) - ); - dataObs.push(observable); - }); - forkJoin(dataObs).subscribe(() => { - this._calculateData(); - }); + ) + ); + forkJoin(dataObs) + .pipe(defaultIfEmpty(null)) + .subscribe(() => { + this._calculateData(); + }); } private _calculateData() { this.chartData = []; - for (const dict of this.dictionaries) { + for (const dict of this.allEntities) { this.chartData.push({ value: dict.entries ? dict.entries.length : 0, color: dict.hexColor, @@ -82,47 +69,11 @@ export class DictionaryListingScreenComponent implements OnInit { }); } this.chartData.sort((a, b) => (a.label < b.label ? -1 : 1)); + this.viewReady = true; } - public get sortingOption(): SortingOption { - return this._sortingService.getSortingOption('dictionary-listing'); - } - - public toggleSort($event) { - this._sortingService.toggleSort('dictionary-listing', $event); - } - - toggleDictSelected($event: MouseEvent, dict: TypeValue) { - $event.stopPropagation(); - const idx = this.selectedDictKeys.indexOf(dict.type); - if (idx === -1) { - this.selectedDictKeys.push(dict.type); - } else { - this.selectedDictKeys.splice(idx, 1); - } - } - - public toggleSelectAll() { - if (this.areSomeDictsSelected) { - this.selectedDictKeys = []; - } else { - this.selectedDictKeys = this.displayedDictionaries.map((dict) => dict.type); - } - } - - public get areAllDictsSelected() { - return this.displayedDictionaries.length !== 0 && this.selectedDictKeys.length === this.displayedDictionaries.length; - } - - public get areSomeDictsSelected() { - return this.selectedDictKeys.length > 0; - } - - public isDictSelected(dict: TypeValue) { - return this.selectedDictKeys.indexOf(dict.type) !== -1; - } - - openAddEditDictionaryDialog(dict?: TypeValue) { + openAddEditDictionaryDialog($event?: MouseEvent, dict?: TypeValue) { + $event?.stopPropagation(); this._dialogService.openAddEditDictionaryDialog(dict, this._appStateService.activeRuleSetId, async (newDictionary) => { if (newDictionary) { await this._appStateService.loadDictionaryData(); @@ -131,11 +82,6 @@ export class DictionaryListingScreenComponent implements OnInit { }); } - openEditDictionaryDialog($event: any, dict: TypeValue) { - $event.stopPropagation(); - this.openAddEditDictionaryDialog(dict); - } - openDeleteDictionaryDialog($event: any, dict: TypeValue) { this._dialogService.openDeleteDictionaryDialog($event, dict, this._appStateService.activeRuleSetId, async () => { await this._appStateService.loadDictionaryData(); diff --git a/apps/red-ui/src/app/modules/admin/screens/file-attributes-listing/file-attributes-listing-screen.component.html b/apps/red-ui/src/app/modules/admin/screens/file-attributes-listing/file-attributes-listing-screen.component.html index 1b9582206..003f33814 100644 --- a/apps/red-ui/src/app/modules/admin/screens/file-attributes-listing/file-attributes-listing-screen.component.html +++ b/apps/red-ui/src/app/modules/admin/screens/file-attributes-listing/file-attributes-listing-screen.component.html @@ -15,18 +15,13 @@
+ - - {{ 'file-attributes-listing.table-header.title' | translate: { length: displayedAttributes.length } }} + {{ 'file-attributes-listing.table-header.title' | translate: { length: displayedEntities.length } }}
-
+
@@ -89,20 +84,20 @@
- + -
-
-
- +
+
+
+
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 7c5ffafeb..7fe68b944 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 @@ -1,23 +1,21 @@ -import { Component, ElementRef, OnInit, ViewChild } from '@angular/core'; +import { Component, ElementRef, Injector, OnInit, ViewChild } from '@angular/core'; import { PermissionsService } from '../../../../services/permissions.service'; -import { FormBuilder, FormGroup } from '@angular/forms'; import { FileAttributeConfig, FileAttributesControllerService } from '@redaction/red-ui-http'; import { AppStateService } from '../../../../state/app-state.service'; import { ActivatedRoute } from '@angular/router'; -import { debounce } from '../../../../utils/debounce'; -import { SortingOption, SortingService } from '../../../../services/sorting.service'; import { AdminDialogService } from '../../services/admin-dialog.service'; +import { BaseListingComponent } from '../../../shared/base/base-listing.component'; @Component({ selector: 'redaction-file-attributes-listing-screen', templateUrl: './file-attributes-listing-screen.component.html', styleUrls: ['./file-attributes-listing-screen.component.scss'] }) -export class FileAttributesListingScreenComponent implements OnInit { - public searchForm: FormGroup; - public attributes: FileAttributeConfig[] = []; - public displayedAttributes: FileAttributeConfig[] = []; - public selectedFileAttributeIds: string[] = []; +export class FileAttributesListingScreenComponent extends BaseListingComponent implements OnInit { + protected readonly _searchKey = 'label'; + protected readonly _selectionKey = 'id'; + protected readonly _sortKey = 'file-attributes-listing'; + public viewReady = false; public loading = false; @@ -25,20 +23,14 @@ export class FileAttributesListingScreenComponent implements OnInit { constructor( public readonly permissionsService: PermissionsService, - public readonly _sortingService: SortingService, - private readonly _formBuilder: FormBuilder, private readonly _fileAttributesService: FileAttributesControllerService, private readonly _appStateService: AppStateService, private readonly _activatedRoute: ActivatedRoute, - private readonly _dialogService: AdminDialogService + private readonly _dialogService: AdminDialogService, + protected readonly _injector: Injector ) { + super(_injector); this._appStateService.activateRuleSet(_activatedRoute.snapshot.params.ruleSetId); - - this.searchForm = this._formBuilder.group({ - query: [''] - }); - - this.searchForm.valueChanges.subscribe((value) => this._executeSearch(value)); } async ngOnInit() { @@ -48,34 +40,15 @@ export class FileAttributesListingScreenComponent implements OnInit { private async _loadData() { try { const response = await this._fileAttributesService.getFileAttributesConfiguration(this._appStateService.activeRuleSetId).toPromise(); - this.attributes = response?.fileAttributeConfigs || []; + this.allEntities = response?.fileAttributeConfigs || []; } catch (e) { } finally { - // Remove potentially deleted items - this.selectedFileAttributeIds = this.selectedFileAttributeIds.filter((id) => !!this.attributes.find((attr) => attr.id === id)); - this._executeSearch(); this.viewReady = true; this.loading = false; } } - public get sortingOption(): SortingOption { - return this._sortingService.getSortingOption('file-attributes-listing'); - } - - public toggleSort($event) { - this._sortingService.toggleSort('file-attributes-listing', $event); - } - - @debounce(200) - private _executeSearch(value?: { query: string }) { - if (!value) { - value = { query: this.searchForm.get('query').value }; - } - this.displayedAttributes = this.attributes.filter((attribute) => attribute.label.toLowerCase().includes(value.query.toLowerCase())); - } - public openAddEditAttributeDialog($event: MouseEvent, fileAttribute?: FileAttributeConfig) { $event.stopPropagation(); this._dialogService.openAddEditFileAttributeDialog(fileAttribute, this._appStateService.activeRuleSetId, async (newValue: FileAttributeConfig) => { @@ -92,42 +65,12 @@ export class FileAttributesListingScreenComponent implements OnInit { if (!!fileAttribute) { await this._fileAttributesService.deleteFileAttribute(this._appStateService.activeRuleSetId, fileAttribute.id).toPromise(); } else { - await this._fileAttributesService.deleteFileAttributes(this.selectedFileAttributeIds, this._appStateService.activeRuleSetId).toPromise(); + await this._fileAttributesService.deleteFileAttributes(this.selectedEntitiesIds, this._appStateService.activeRuleSetId).toPromise(); } await this._loadData(); }); } - public toggleAttributeSelected($event: MouseEvent, attribute: FileAttributeConfig) { - $event.stopPropagation(); - const idx = this.selectedFileAttributeIds.indexOf(attribute.id); - if (idx === -1) { - this.selectedFileAttributeIds.push(attribute.id); - } else { - this.selectedFileAttributeIds.splice(idx, 1); - } - } - - public toggleSelectAll() { - if (this.areSomeAttributesSelected) { - this.selectedFileAttributeIds = []; - } else { - this.selectedFileAttributeIds = this.displayedAttributes.map((a) => a.id); - } - } - - public get areAllAttributesSelected() { - return this.displayedAttributes.length !== 0 && this.selectedFileAttributeIds.length === this.displayedAttributes.length; - } - - public get areSomeAttributesSelected() { - return this.selectedFileAttributeIds.length > 0; - } - - public isAttributeSelected(attribute: FileAttributeConfig) { - return this.selectedFileAttributeIds.indexOf(attribute.id) !== -1; - } - public importCSV(files: FileList | File[]) { const csvFile = files[0]; this._fileInput.nativeElement.value = null; diff --git a/apps/red-ui/src/app/modules/admin/screens/rule-sets-listing/rule-sets-listing-screen.component.html b/apps/red-ui/src/app/modules/admin/screens/rule-sets-listing/rule-sets-listing-screen.component.html index 72be0a00e..452c1d81b 100644 --- a/apps/red-ui/src/app/modules/admin/screens/rule-sets-listing/rule-sets-listing-screen.component.html +++ b/apps/red-ui/src/app/modules/admin/screens/rule-sets-listing/rule-sets-listing-screen.component.html @@ -30,13 +30,13 @@
- + - {{ 'project-templates-listing.table-header.title' | translate: { length: displayedRuleSets.length } }} + {{ 'project-templates-listing.table-header.title' | translate: { length: displayedEntities.length } }}
-
+
- + @@ -87,12 +87,12 @@
-
-
- +
+
+
diff --git a/apps/red-ui/src/app/modules/admin/screens/rule-sets-listing/rule-sets-listing-screen.component.ts b/apps/red-ui/src/app/modules/admin/screens/rule-sets-listing/rule-sets-listing-screen.component.ts index 105a1afd6..bde0b264f 100644 --- a/apps/red-ui/src/app/modules/admin/screens/rule-sets-listing/rule-sets-listing-screen.component.ts +++ b/apps/red-ui/src/app/modules/admin/screens/rule-sets-listing/rule-sets-listing-screen.component.ts @@ -1,57 +1,44 @@ -import { Component, OnInit } from '@angular/core'; -import { SortingOption, SortingService } from '../../../../services/sorting.service'; +import { Component, Injector, OnInit } from '@angular/core'; import { AppStateService } from '../../../../state/app-state.service'; import { PermissionsService } from '../../../../services/permissions.service'; -import { FormBuilder, FormGroup } from '@angular/forms'; -import { debounce } from '../../../../utils/debounce'; -import { RuleSetModel } from '@redaction/red-ui-http'; import { UserPreferenceService } from '../../../../services/user-preference.service'; import { AdminDialogService } from '../../services/admin-dialog.service'; +import { BaseListingComponent } from '../../../shared/base/base-listing.component'; +import { RuleSetModel } from '@redaction/red-ui-http'; @Component({ selector: 'redaction-rule-sets-listing-screen', templateUrl: './rule-sets-listing-screen.component.html', styleUrls: ['./rule-sets-listing-screen.component.scss'] }) -export class RuleSetsListingScreenComponent implements OnInit { - public ruleSets: RuleSetModel[]; - public displayedRuleSets: RuleSetModel[]; - public selectedRuleSetIds: string[] = []; - public searchForm: FormGroup; +export class RuleSetsListingScreenComponent extends BaseListingComponent implements OnInit { + protected readonly _searchKey = 'name'; + protected readonly _selectionKey = 'ruleSetId'; + protected readonly _sortKey = 'rule-sets-listing'; constructor( private readonly _dialogService: AdminDialogService, - private readonly _sortingService: SortingService, - private readonly _formBuilder: FormBuilder, private readonly _appStateService: AppStateService, public readonly permissionsService: PermissionsService, - public readonly userPreferenceService: UserPreferenceService + public readonly userPreferenceService: UserPreferenceService, + protected readonly _injector: Injector ) { - this.searchForm = this._formBuilder.group({ - query: [''] - }); - - this.searchForm.valueChanges.subscribe((value) => this._executeSearch(value)); + super(_injector); } ngOnInit(): void { this.loadRuleSetsData(); } - @debounce(200) - private _executeSearch(value: { query: string }) { - this.displayedRuleSets = this.ruleSets.filter((pt) => pt.name.toLowerCase().includes(value.query.toLowerCase())); - } - public loadRuleSetsData() { this._appStateService.reset(); - this.ruleSets = this._appStateService.ruleSets; - this.displayedRuleSets = [...this.ruleSets]; + this.allEntities = this._appStateService.ruleSets; + this._executeSearch(); this._loadRuleSetStats(); } private _loadRuleSetStats() { - this.ruleSets.forEach((rs) => { + this.allEntities.forEach((rs) => { const dictionaries = this._appStateService.dictionaryData[rs.ruleSetId]; if (dictionaries) { rs.dictionariesCount = Object.keys(dictionaries) @@ -64,45 +51,7 @@ export class RuleSetsListingScreenComponent implements OnInit { }); } - public get sortingOption(): SortingOption { - return this._sortingService.getSortingOption('rule-sets-listing'); - } - - public toggleSort($event) { - this._sortingService.toggleSort('rule-sets-listing', $event); - } - - toggleTemplateSelected($event: MouseEvent, ruleSet: RuleSetModel) { - $event.stopPropagation(); - const idx = this.selectedRuleSetIds.indexOf(ruleSet.ruleSetId); - if (idx === -1) { - this.selectedRuleSetIds.push(ruleSet.ruleSetId); - } else { - this.selectedRuleSetIds.splice(idx, 1); - } - } - - public toggleSelectAll() { - if (this.areSomeRuleSetsSelected) { - this.selectedRuleSetIds = []; - } else { - this.selectedRuleSetIds = this.displayedRuleSets.map((rs) => rs.ruleSetId); - } - } - - public get areAllRuleSetsSelected() { - return this.displayedRuleSets.length !== 0 && this.selectedRuleSetIds.length === this.displayedRuleSets.length; - } - - public get areSomeRuleSetsSelected() { - return this.selectedRuleSetIds.length > 0; - } - - public isRuleSetSelected(ruleSet: RuleSetModel) { - return this.selectedRuleSetIds.indexOf(ruleSet.ruleSetId) !== -1; - } - - openAddRuleSetDialog() { + public openAddRuleSetDialog() { this._dialogService.openAddEditRuleSetDialog(null, async (newRuleSet) => { if (newRuleSet) { this.loadRuleSetsData(); diff --git a/apps/red-ui/src/app/modules/admin/screens/user-listing/user-listing-screen.component.html b/apps/red-ui/src/app/modules/admin/screens/user-listing/user-listing-screen.component.html index bd27526a2..a3e32833a 100644 --- a/apps/red-ui/src/app/modules/admin/screens/user-listing/user-listing-screen.component.html +++ b/apps/red-ui/src/app/modules/admin/screens/user-listing/user-listing-screen.component.html @@ -28,23 +28,23 @@
- +
- {{ 'user-listing.table-header.title' | translate: { length: displayedUsers.length } }} + {{ 'user-listing.table-header.title' | translate: { length: displayedEntities.length } }} - +
- + -
-
-
- +
+
+
+
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 8c8253bb2..0af279818 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 @@ -1,53 +1,44 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, Injector, OnInit } from '@angular/core'; import { PermissionsService } from '../../../../services/permissions.service'; import { UserService } from '../../../../services/user.service'; import { User, UserControllerService } from '@redaction/red-ui-http'; -import { FormBuilder, FormGroup } from '@angular/forms'; -import { debounce } from '../../../../utils/debounce'; import { AdminDialogService } from '../../services/admin-dialog.service'; import { TranslateService } from '@ngx-translate/core'; import { DoughnutChartConfig } from '../../../shared/components/simple-doughnut-chart/simple-doughnut-chart.component'; import { TranslateChartService } from '../../../../services/translate-chart.service'; +import { BaseListingComponent } from '../../../shared/base/base-listing.component'; @Component({ selector: 'redaction-user-listing-screen', templateUrl: './user-listing-screen.component.html', styleUrls: ['./user-listing-screen.component.scss'] }) -export class UserListingScreenComponent implements OnInit { +export class UserListingScreenComponent extends BaseListingComponent implements OnInit { + protected readonly _selectionKey = 'userId'; + public viewReady = false; public loading = false; public collapsedDetails = false; public chartData: DoughnutChartConfig[] = []; - public users: User[]; - public displayedUsers: User[] = []; - public searchForm: FormGroup; - public selectedUsersIds: string[] = []; constructor( public readonly permissionsService: PermissionsService, public readonly userService: UserService, - private readonly _formBuilder: FormBuilder, private readonly _translateService: TranslateService, private readonly _adminDialogService: AdminDialogService, private readonly _userControllerService: UserControllerService, - private readonly _translateChartService: TranslateChartService + private readonly _translateChartService: TranslateChartService, + protected readonly _injector: Injector ) { - this.searchForm = this._formBuilder.group({ - query: [''] - }); - - this.searchForm.valueChanges.subscribe(() => this._executeSearch()); + super(_injector); } public async ngOnInit() { await this._loadData(); } - @debounce(200) - private _executeSearch() { - const value = this.searchForm.get('query').value; - this.displayedUsers = this.users.filter((user) => this.userService.getName(user).toLowerCase().includes(value.toLowerCase())); + protected _searchField(user: any): string { + return this.userService.getName(user); } public openAddEditUserDialog($event: MouseEvent, user?: User) { @@ -77,7 +68,7 @@ export class UserListingScreenComponent implements OnInit { } private async _loadData() { - this.users = (await this._userControllerService.getAllUsers({ requestId: new Date().toISOString() }).toPromise()).users; + this.allEntities = (await this._userControllerService.getAllUsers({ requestId: new Date().toISOString() }).toPromise()).users; this._executeSearch(); this._computeStats(); this.viewReady = true; @@ -88,32 +79,32 @@ export class UserListingScreenComponent implements OnInit { this.chartData = this._translateChartService.translateRoles( [ { - value: this.users.filter((user) => !this.userService.isActive(user)).length, + value: this.allEntities.filter((user) => !this.userService.isActive(user)).length, color: 'INACTIVE', label: 'INACTIVE' }, { - value: this.users.filter((user) => user.roles.length === 1 && user.roles[0] === 'RED_USER').length, + value: this.allEntities.filter((user) => user.roles.length === 1 && user.roles[0] === 'RED_USER').length, color: 'REGULAR', label: 'REGULAR' }, { - value: this.users.filter((user) => this.userService.isManager(user) && !this.userService.isAdmin(user)).length, + value: this.allEntities.filter((user) => this.userService.isManager(user) && !this.userService.isAdmin(user)).length, color: 'MANAGER', label: 'RED_MANAGER' }, { - value: this.users.filter((user) => this.userService.isManager(user) && this.userService.isAdmin(user)).length, + value: this.allEntities.filter((user) => this.userService.isManager(user) && this.userService.isAdmin(user)).length, color: 'MANAGER_ADMIN', label: 'MANAGER_ADMIN' }, { - value: this.users.filter((user) => this.userService.isUserAdmin(user) && !this.userService.isAdmin(user)).length, + value: this.allEntities.filter((user) => this.userService.isUserAdmin(user) && !this.userService.isAdmin(user)).length, color: 'USER_ADMIN', label: 'RED_USER_ADMIN' }, { - value: this.users.filter((user) => this.userService.isAdmin(user) && !this.userService.isManager(user)).length, + value: this.allEntities.filter((user) => this.userService.isAdmin(user) && !this.userService.isManager(user)).length, color: 'ADMIN', label: 'RED_ADMIN' } @@ -136,41 +127,11 @@ export class UserListingScreenComponent implements OnInit { this.collapsedDetails = !this.collapsedDetails; } - toggleUserSelected($event: MouseEvent, user: User) { - $event.stopPropagation(); - const idx = this.selectedUsersIds.indexOf(user.userId); - if (idx === -1) { - this.selectedUsersIds.push(user.userId); - } else { - this.selectedUsersIds.splice(idx, 1); - } - } - - public toggleSelectAll() { - if (this.areSomeUsersSelected) { - this.selectedUsersIds = []; - } else { - this.selectedUsersIds = this.displayedUsers.map((user) => user.userId); - } - } - - public get areAllUsersSelected() { - return this.displayedUsers.length !== 0 && this.selectedUsersIds.length === this.displayedUsers.length; - } - - public get areSomeUsersSelected() { - return this.selectedUsersIds.length > 0; - } - - public isUserSelected(user: User) { - return this.selectedUsersIds.indexOf(user.userId) !== -1; - } - public async bulkDelete() { - this.openDeleteUserDialog(this.users.filter((u) => this.isUserSelected(u))); + this.openDeleteUserDialog(this.allEntities.filter((u) => this.isEntitySelected(u))); } public get canDeleteSelected(): boolean { - return this.selectedUsersIds.indexOf(this.userService.userId) === -1; + return this.selectedEntitiesIds.indexOf(this.userService.userId) === -1; } } diff --git a/apps/red-ui/src/app/modules/projects/screens/project-listing-screen/project-listing-screen.component.html b/apps/red-ui/src/app/modules/projects/screens/project-listing-screen/project-listing-screen.component.html index 13b48099b..c037ce2a2 100644 --- a/apps/red-ui/src/app/modules/projects/screens/project-listing-screen/project-listing-screen.component.html +++ b/apps/red-ui/src/app/modules/projects/screens/project-listing-screen/project-listing-screen.component.html @@ -48,13 +48,13 @@
- {{ 'project-listing.table-header.title' | translate: { length: displayedProjects.length || 0 } }} + {{ 'project-listing.table-header.title' | translate: { length: displayedEntities.length || 0 } }}
- +
- - diff --git a/apps/red-ui/src/app/modules/projects/screens/project-listing-screen/project-listing-screen.component.ts b/apps/red-ui/src/app/modules/projects/screens/project-listing-screen/project-listing-screen.component.ts index ba7d43e8f..5a00e5daf 100644 --- a/apps/red-ui/src/app/modules/projects/screens/project-listing-screen/project-listing-screen.component.ts +++ b/apps/red-ui/src/app/modules/projects/screens/project-listing-screen/project-listing-screen.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectorRef, Component, OnDestroy, OnInit, ViewChild } from '@angular/core'; +import { Component, Injector, OnDestroy, OnInit, ViewChild } from '@angular/core'; import { FileManagementControllerService, Project, RuleSetModel } from '@redaction/red-ui-http'; import { AppStateService } from '../../../../state/app-state.service'; import { UserService } from '../../../../services/user.service'; @@ -7,14 +7,12 @@ import { groupBy } from '../../../../utils/functions'; import { FilterModel } from '../../../shared/components/filter/model/filter.model'; import { annotationFilterChecker, - getFilteredEntities, processFilters, projectMemberChecker, projectStatusChecker, ruleSetChecker } from '../../../shared/components/filter/utils/filter-utils'; import { TranslateService } from '@ngx-translate/core'; -import { SortingOption, SortingService } from '../../../../services/sorting.service'; import { PermissionsService } from '../../../../services/permissions.service'; import { ProjectWrapper } from '../../../../state/model/project.wrapper'; import { Subscription, timer } from 'rxjs'; @@ -23,21 +21,22 @@ import { TranslateChartService } from '../../../../services/translate-chart.serv import { RedactionFilterSorter } from '../../../../utils/sorters/redaction-filter-sorter'; import { StatusSorter } from '../../../../utils/sorters/status-sorter'; import { Router } from '@angular/router'; -import { FormBuilder, FormGroup } from '@angular/forms'; -import { debounce } from '../../../../utils/debounce'; import { FilterComponent } from '../../../shared/components/filter/filter.component'; import { ProjectsDialogService } from '../../services/projects-dialog.service'; +import { BaseListingComponent } from '../../../shared/base/base-listing.component'; @Component({ selector: 'redaction-project-listing-screen', templateUrl: './project-listing-screen.component.html', styleUrls: ['./project-listing-screen.component.scss'] }) -export class ProjectListingScreenComponent implements OnInit, OnDestroy { +export class ProjectListingScreenComponent extends BaseListingComponent implements OnInit, OnDestroy { + protected readonly _searchKey = 'name'; + protected readonly _sortKey = 'project-listing'; + public projectsChartData: DoughnutChartConfig[] = []; public documentsChartData: DoughnutChartConfig[] = []; - public searchForm: FormGroup; public actionMenuOpen: boolean; public statusFilters: FilterModel[]; @@ -51,7 +50,6 @@ export class ProjectListingScreenComponent implements OnInit, OnDestroy { statusFilters: [] }; - public displayedProjects: ProjectWrapper[] = []; private projectAutoUpdateTimer: Subscription; @ViewChild('statusFilter') private _statusFilterComponent: FilterComponent; @@ -60,36 +58,32 @@ export class ProjectListingScreenComponent implements OnInit, OnDestroy { @ViewChild('ruleSetFilter') private _ruleSetFilterComponent: FilterComponent; constructor( - public readonly appStateService: AppStateService, + private readonly _appStateService: AppStateService, public readonly userService: UserService, public readonly permissionsService: PermissionsService, - private readonly _changeDetectorRef: ChangeDetectorRef, private readonly _dialogService: ProjectsDialogService, private readonly _translateService: TranslateService, private readonly _router: Router, - public readonly sortingService: SortingService, public readonly translateChartService: TranslateChartService, - private readonly _formBuilder: FormBuilder, - private readonly _fileManagementControllerService: FileManagementControllerService + private readonly _fileManagementControllerService: FileManagementControllerService, + protected readonly _injector: Injector ) { - this.appStateService.reset(); - this.searchForm = this._formBuilder.group({ - query: [''] - }); - - this.searchForm.valueChanges.subscribe((value) => this._executeSearch(value)); + super(_injector); + this._appStateService.reset(); + this._loadEntitiesFromState(); } public ngOnInit(): void { this.projectAutoUpdateTimer = timer(0, 10000) .pipe( tap(async () => { - await this.appStateService.loadAllProjects(); + await this._appStateService.loadAllProjects(); + this._loadEntitiesFromState(); }) ) .subscribe(); this._calculateData(); - this.appStateService.fileChanged.subscribe(() => { + this._appStateService.fileChanged.subscribe(() => { this._calculateData(); }); } @@ -98,37 +92,26 @@ export class ProjectListingScreenComponent implements OnInit, OnDestroy { this.projectAutoUpdateTimer.unsubscribe(); } - public get hasActiveFilters() { - return ( - this._statusFilterComponent?.hasActiveFilters || - this._peopleFilterComponent?.hasActiveFilters || - this._needsWorkFilterComponent?.hasActiveFilters || - this._ruleSetFilterComponent?.hasActiveFilters || - this.searchForm.get('query').value - ); + private _loadEntitiesFromState() { + this.allEntities = this._appStateService.allProjects; } public get noData() { - return this.appStateService.allProjects?.length === 0; + return this.allEntities.length === 0; } - public resetFilters() { - this._statusFilterComponent.deactivateAllFilters(); - this._peopleFilterComponent.deactivateAllFilters(); - this._needsWorkFilterComponent.deactivateAllFilters(); - this._ruleSetFilterComponent.deactivateAllFilters(); - this.filtersChanged(); - this.searchForm.reset({ query: '' }); + protected get filterComponents(): FilterComponent[] { + return [this._statusFilterComponent, this._peopleFilterComponent, this._needsWorkFilterComponent, this._ruleSetFilterComponent]; } private _calculateData() { this._computeAllFilters(); - this._filterProjects(); + this._filterEntities(); this.projectsChartData = [ - { value: this.activeProjects, color: 'ACTIVE', label: 'active' }, - { value: this.inactiveProjects, color: 'DELETED', label: 'archived' } + { value: this.activeProjectsCount, color: 'ACTIVE', label: 'active' }, + { value: this.inactiveProjectsCount, color: 'DELETED', label: 'archived' } ]; - const groups = groupBy(this.appStateService.aggregatedFiles, 'status'); + const groups = groupBy(this._appStateService.aggregatedFiles, 'status'); this.documentsChartData = []; for (const key of Object.keys(groups)) { this.documentsChartData.push({ @@ -146,16 +129,12 @@ export class ProjectListingScreenComponent implements OnInit, OnDestroy { return this.userService.user; } - public get sortingOption(): SortingOption { - return this.sortingService.getSortingOption('project-listing'); + public get activeProjectsCount() { + return this.allEntities.filter((p) => p.project.status === Project.StatusEnum.ACTIVE).length; } - public get activeProjects() { - return this.appStateService.allProjects.reduce((i, p) => i + (p.project.status === Project.StatusEnum.ACTIVE ? 1 : 0), 0); - } - - public get inactiveProjects() { - return this.appStateService.allProjects.length - this.activeProjects; + public get inactiveProjectsCount() { + return this.allEntities.length - this.activeProjectsCount; } public documentCount(project: ProjectWrapper) { @@ -171,7 +150,7 @@ export class ProjectListingScreenComponent implements OnInit, OnDestroy { } public getRuleSet(pw: ProjectWrapper): RuleSetModel { - return this.appStateService.getRuleSetById(pw.project.ruleSetId); + return this._appStateService.getRuleSetById(pw.project.ruleSetId); } public openAddProjectDialog(): void { @@ -193,7 +172,7 @@ export class ProjectListingScreenComponent implements OnInit, OnDestroy { const allDistinctPeople = new Set(); const allDistinctNeedsWork = new Set(); const allDistinctRuleSets = new Set(); - this.appStateService.allProjects.forEach((entry) => { + this.allEntities.forEach((entry) => { // all people entry.project.memberIds.forEach((memberId) => allDistinctPeople.add(memberId)); // file statuses @@ -248,25 +227,14 @@ export class ProjectListingScreenComponent implements OnInit, OnDestroy { allDistinctRuleSets.forEach((ruleSetId) => { ruleSetFilters.push({ key: ruleSetId, - label: this.appStateService.getRuleSetById(ruleSetId).name + label: this._appStateService.getRuleSetById(ruleSetId).name }); }); this.ruleSetFilters = processFilters(this.ruleSetFilters, ruleSetFilters); } - filtersChanged(filters?: { [key: string]: FilterModel[] }): void { - if (filters) { - for (const key of Object.keys(filters)) { - for (let idx = 0; idx < this[key].length; ++idx) { - this[key][idx] = filters[key][idx]; - } - } - } - this._filterProjects(); - } - - private get _filteredProjects(): ProjectWrapper[] { - const filters = [ + protected get filters(): { values: FilterModel[]; checker: Function; matchAll?: boolean; checkerArgs?: any }[] { + return [ { values: this.statusFilters, checker: projectStatusChecker }, { values: this.peopleFilters, checker: projectMemberChecker }, { @@ -277,25 +245,15 @@ export class ProjectListingScreenComponent implements OnInit, OnDestroy { }, { values: this.ruleSetFilters, checker: ruleSetChecker } ]; - return getFilteredEntities(this.appStateService.allProjects, filters); } - private _filterProjects() { + protected preFilter() { this.detailsContainerFilters = { statusFilters: this.statusFilters.map((f) => ({ ...f })) }; - this.displayedProjects = this._filteredProjects.filter((project) => - project.name.toLowerCase().includes(this.searchForm.get('query').value.toLowerCase()) - ); - this._changeDetectorRef.detectChanges(); } - @debounce(200) - private _executeSearch(value: { query: string }) { - this.displayedProjects = this._filteredProjects.filter((project) => project.name.toLowerCase().includes(value.query.toLowerCase())); - } - - actionPerformed(pw: ProjectWrapper) { + public actionPerformed(pw: ProjectWrapper) { this._calculateData(); } } diff --git a/apps/red-ui/src/app/modules/projects/screens/project-overview-screen/project-overview-screen.component.html b/apps/red-ui/src/app/modules/projects/screens/project-overview-screen/project-overview-screen.component.html index c4f6ba38c..f8c08808d 100644 --- a/apps/red-ui/src/app/modules/projects/screens/project-overview-screen/project-overview-screen.component.html +++ b/apps/red-ui/src/app/modules/projects/screens/project-overview-screen/project-overview-screen.component.html @@ -1,4 +1,4 @@ -
+