From 6932fa6b30eb2807eb582c956ad5016f28930395 Mon Sep 17 00:00:00 2001 From: Dan Percic Date: Wed, 14 Jul 2021 23:12:28 +0300 Subject: [PATCH 1/6] remove unused methods, add AutoUnsubscribeComponent --- .../edit-color-dialog.component.ts | 8 +- .../active-fields-listing.component.html | 2 +- .../active-fields-listing.component.ts | 24 ++- ...ttributes-csv-import-dialog.component.html | 75 ++------- ...-attributes-csv-import-dialog.component.ts | 6 +- .../default-colors-screen.component.ts | 4 +- .../dictionary-listing-screen.component.html | 45 ++--- .../dictionary-listing-screen.component.ts | 22 ++- .../digital-signature-screen.component.ts | 21 +-- ...r-attributes-listing-screen.component.html | 2 +- ...ier-attributes-listing-screen.component.ts | 9 +- ...er-templates-listing-screen.component.html | 4 +- ...sier-templates-listing-screen.component.ts | 15 +- ...e-attributes-listing-screen.component.html | 2 +- ...ile-attributes-listing-screen.component.ts | 12 +- .../screens/rules/rules-screen.component.ts | 24 +-- .../smtp-config-screen.component.ts | 38 ++--- .../screens/trash/trash-screen.component.ts | 15 +- .../user-listing-screen.component.html | 43 ++--- .../user-listing-screen.component.ts | 9 +- ...dossier-overview-bulk-actions.component.ts | 42 +++-- .../dossier-details.component.html | 8 +- .../dossier-details.component.ts | 5 +- .../dossier-listing-details.component.html | 1 - .../team-members-manager.component.ts | 14 +- .../add-dossier-dialog.component.ts | 17 +- ...sign-reviewer-approver-dialog.component.ts | 10 +- ...edit-dossier-download-package.component.ts | 2 +- .../edit-dossier-general-info.component.ts | 4 +- .../dossier-listing-screen.component.ts | 90 +++++----- .../dossier-overview-screen.component.html | 5 +- .../dossier-overview-screen.component.ts | 79 +++------ .../file-preview-screen.component.ts | 2 +- .../dossier/services/dossiers.service.ts | 20 ++- .../services/manual-annotation.service.ts | 65 ++------ .../shared/base/auto-unsubscribe.component.ts | 29 ++++ .../shared/base/base-listing.component.ts | 64 ++++--- .../file-download-btn.component.ts | 24 +-- .../simple-doughnut-chart.component.html | 14 +- .../simple-doughnut-chart.component.ts | 48 ++---- .../modules/shared/services/filter.service.ts | 10 +- .../shared/services/screen-state.service.ts | 69 +++----- .../modules/shared/services/search.service.ts | 12 +- .../app/services/user-preference.service.ts | 27 ++- .../red-ui/src/app/state/app-state.service.ts | 157 ++++++++---------- .../src/app/utils/sorters/status-sorter.ts | 2 +- 46 files changed, 466 insertions(+), 734 deletions(-) create mode 100644 apps/red-ui/src/app/modules/shared/base/auto-unsubscribe.component.ts diff --git a/apps/red-ui/src/app/modules/admin/dialogs/edit-color-dialog/edit-color-dialog.component.ts b/apps/red-ui/src/app/modules/admin/dialogs/edit-color-dialog/edit-color-dialog.component.ts index 274d9789f..37ce4623e 100644 --- a/apps/red-ui/src/app/modules/admin/dialogs/edit-color-dialog/edit-color-dialog.component.ts +++ b/apps/red-ui/src/app/modules/admin/dialogs/edit-color-dialog/edit-color-dialog.component.ts @@ -47,15 +47,11 @@ export class EditColorDialogComponent { }; try { - await this._dictionaryControllerService - .setColors(colors, this._dossierTemplateId) - .toPromise(); + await this._dictionaryControllerService.setColors(colors, this._dossierTemplateId).toPromise(); this._dialogRef.close(true); this._notificationService.showToastNotification( this._translateService.instant('edit-color-dialog.success', { - color: this._translateService.instant( - 'default-colors-screen.types.' + this.colorKey - ) + color: this._translateService.instant('default-colors-screen.types.' + this.colorKey) }) ); } catch (e) { 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 index a77d8178e..7dc532184 100644 --- 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 @@ -7,7 +7,7 @@ > - {{ 'file-attributes-csv-import.table-header.title' | translate: { length: allEntities.length } }} + {{ 'file-attributes-csv-import.table-header.title' | translate: { length: (allEntities$ | async)?.length } }} 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 abd4dbd85..e1301401a 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 @@ -23,38 +23,36 @@ export class ActiveFieldsListingComponent extends BaseListingComponent im constructor(protected readonly _injector: Injector) { super(_injector); - this._screenStateService.setIdKey('csvColumn'); } ngOnChanges(changes: SimpleChanges): void { if (changes.entities) { - this._screenStateService.setEntities(this.entities); - this._screenStateService.setDisplayedEntities(this.entities); - this._screenStateService.updateSelection(); + this.screenStateService.setEntities(this.entities); + this.screenStateService.setDisplayedEntities(this.entities); + this.screenStateService.updateSelection(); } } deactivateSelection() { this.allEntities.filter(field => this.isSelected(field)).forEach(field => (field.primaryAttribute = false)); - this._screenStateService.setEntities([...this.allEntities.filter(field => !this.isSelected(field))]); + this.screenStateService.setEntities(this.allEntities.filter(field => !this.isSelected(field))); this.entitiesChange.emit(this.allEntities); - this._screenStateService.setSelectedEntitiesIds([]); + this.screenStateService.setSelectedEntities([]); } setAttributeForSelection(attribute: string, value: any) { - for (const csvColumn of this._screenStateService.selectedEntitiesIds) { - this.allEntities.find(f => f.csvColumn === csvColumn)[attribute] = value; + for (const item of this.screenStateService.selectedEntities) { + this.allEntities.find(f => f.csvColumn === item.csvColumn)[attribute] = value; } } togglePrimary(field: Field) { if (field.primaryAttribute) { field.primaryAttribute = false; - } else { - for (const f of this.allEntities) { - f.primaryAttribute = false; - } - field.primaryAttribute = true; + return; } + + for (const f of this.allEntities) f.primaryAttribute = false; + field.primaryAttribute = true; } } 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 71d54c5c2..1b5300bfd 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 @@ -4,37 +4,25 @@
-
- {{ csvFile.name }} -
+
{{ csvFile.name }}
- {{ - 'file-attributes-csv-import.total-rows' - | translate: { rows: parseResult?.data?.length } - }} + {{ 'file-attributes-csv-import.total-rows' | translate: { rows: parseResult?.data?.length } }}
- {{ - 'file-attributes-csv-import.key-column' | translate - }} + {{ 'file-attributes-csv-import.key-column' | translate }} - + {{ field }} @@ -44,9 +32,7 @@
{{ - 'file-attributes-csv-import.available' - | translate: { value: parseResult?.meta?.fields.length } + 'file-attributes-csv-import.available' | translate: { value: parseResult?.meta?.fields.length } }} {{ - 'file-attributes-csv-import.selected' - | translate: { value: activeFields.length } + 'file-attributes-csv-import.selected' | translate: { value: activeFields.length } }}
@@ -108,7 +90,7 @@
-
- {{ getEntries(field.csvColumn) }} entries -
-
- Sample: {{ getSample(field.csvColumn) }} -
+
{{ getEntries(field.csvColumn) }} entries
+
Sample: {{ getSample(field.csvColumn) }}
@@ -146,10 +124,7 @@ >
- {{ - 'file-attributes-csv-import.csv-column' + - (previewExpanded ? '' : '-preview') | translate - }} + {{ 'file-attributes-csv-import.csv-column' + (previewExpanded ? '' : '-preview') | translate }}
-
+
- {{ - 'file-attributes-csv-import.no-sample-data-for' - | translate: { column: hoveredColumn } - }} + {{ 'file-attributes-csv-import.no-sample-data-for' | translate: { column: hoveredColumn } }}
{{ row }} @@ -184,12 +152,7 @@
- @@ -198,9 +161,5 @@
- + 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 e1ea2765e..b09b644ae 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 @@ -26,7 +26,6 @@ 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'], providers: [FilterService, SearchService, ScreenStateService, SortingService] @@ -44,7 +43,6 @@ export class FileAttributesCsvImportDialogComponent extends BaseListingComponent keepPreview = false; columnSample = []; initialParseConfig: { delimiter?: string; encoding?: string } = {}; - protected readonly _searchKey = 'csvColumn'; constructor( private readonly _appStateService: AppStateService, @@ -96,8 +94,8 @@ export class FileAttributesCsvImportDialogComponent extends BaseListingComponent this.parseResult.meta.fields = Object.keys(this.parseResult.data[0]); } - this._screenStateService.setEntities(this.parseResult.meta.fields.map(field => this._buildAttribute(field))); - this._screenStateService.setDisplayedEntities(this.allEntities); + this.screenStateService.setEntities(this.parseResult.meta.fields.map(field => this._buildAttribute(field))); + this.screenStateService.setDisplayedEntities(this.allEntities); this.activeFields = []; for (const entity of this.allEntities) { 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 cc63d6124..8b21ef348 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 @@ -35,7 +35,7 @@ export class DefaultColorsScreenComponent protected readonly _injector: Injector ) { super(_injector); - this._sortingService.setScreenName(ScreenNames.DEFAULT_COLORS); + this.sortingService.setScreenName(ScreenNames.DEFAULT_COLORS); _appStateService.activateDossierTemplate(_activatedRoute.snapshot.params.dossierTemplateId); } @@ -62,7 +62,7 @@ export class DefaultColorsScreenComponent this._loadingService.start(); const data = await this._dictionaryControllerService.getColors(this._appStateService.activeDossierTemplateId).toPromise(); this._colorsObj = data; - this._screenStateService.setEntities( + this.screenStateService.setEntities( 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 13ac5fe9e..b0b8c30a4 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 @@ -25,17 +25,12 @@
- {{ - 'dictionary-listing.table-header.title' - | translate: { length: (displayedEntities$ | async)?.length } - }} + {{ 'dictionary-listing.table-header.title' | translate: { length: (displayedEntities$ | async)?.length } }} @@ -64,11 +59,7 @@
-
+
- +
-
+
{{ dict.label }} @@ -153,16 +135,13 @@
- +
{ this._loadingService.start(); - await this._dictionaryControllerService.deleteTypes(types, this._appStateService.activeDossierTemplateId).toPromise(); - this._screenStateService.setSelectedEntitiesIds([]); + await this._dictionaryControllerService + .deleteTypes( + types.map(t => t.type), + this._appStateService.activeDossierTemplateId + ) + .toPromise(); + this.screenStateService.setSelectedEntities([]); await this._appStateService.loadDictionaryData(); this._loadDictionaryData(false); this._calculateData(); @@ -88,15 +92,15 @@ export class DictionaryListingScreenComponent extends BaseListingComponent !d.virtual); if (!loadEntries) - this._screenStateService.setEntities( + this.screenStateService.setEntities( entities.map(dict => { dict.entries = this.allEntities.find(d => d.type === dict.type)?.entries || []; return dict; }) ); - else this._screenStateService.setEntities(entities); + else this.screenStateService.setEntities(entities); - this._screenStateService.setDisplayedEntities(this.allEntities); + this.screenStateService.setDisplayedEntities(this.allEntities); if (!loadEntries) return; diff --git a/apps/red-ui/src/app/modules/admin/screens/digital-signature/digital-signature-screen.component.ts b/apps/red-ui/src/app/modules/admin/screens/digital-signature/digital-signature-screen.component.ts index 1832c4977..0ebbfb000 100644 --- a/apps/red-ui/src/app/modules/admin/screens/digital-signature/digital-signature-screen.component.ts +++ b/apps/red-ui/src/app/modules/admin/screens/digital-signature/digital-signature-screen.component.ts @@ -29,10 +29,7 @@ export class DigitalSignatureScreenComponent { } get hasDigitalSignatureSet() { - return ( - this.digitalSignatureExists || - !!this.digitalSignatureForm.get('base64EncodedPrivateKey').value - ); + return this.digitalSignatureExists || !!this.digitalSignatureForm.get('base64EncodedPrivateKey').value; } saveDigitalSignature() { @@ -58,17 +55,13 @@ export class DigitalSignatureScreenComponent { error => { if (error.status === 400) { this._notificationService.showToastNotification( - this._translateService.instant( - 'digital-signature-screen.action.certificate-not-valid-error' - ), + this._translateService.instant('digital-signature-screen.action.certificate-not-valid-error'), null, NotificationType.ERROR ); } else { this._notificationService.showToastNotification( - this._translateService.instant( - 'digital-signature-screen.action.save-error' - ), + this._translateService.instant('digital-signature-screen.action.save-error'), null, NotificationType.ERROR ); @@ -82,9 +75,7 @@ export class DigitalSignatureScreenComponent { () => { this.loadDigitalSignatureAndInitializeForm(); this._notificationService.showToastNotification( - this._translateService.instant( - 'digital-signature-screen.action.delete-success' - ), + this._translateService.instant('digital-signature-screen.action.delete-success'), null, NotificationType.SUCCESS ); @@ -139,9 +130,7 @@ export class DigitalSignatureScreenComponent { certificateName: [this.digitalSignature.certificateName, Validators.required], contactInfo: this.digitalSignature.contactInfo, location: this.digitalSignature.location, - keySecret: this.digitalSignatureExists - ? null - : [this.digitalSignature.password, Validators.required], + keySecret: this.digitalSignatureExists ? null : [this.digitalSignature.password, Validators.required], reason: this.digitalSignature.reason, base64EncodedPrivateKey: this.digitalSignatureExists ? null diff --git a/apps/red-ui/src/app/modules/admin/screens/dossier-attributes-listing/dossier-attributes-listing-screen.component.html b/apps/red-ui/src/app/modules/admin/screens/dossier-attributes-listing/dossier-attributes-listing-screen.component.html index 4269a49d2..77c7a7314 100644 --- a/apps/red-ui/src/app/modules/admin/screens/dossier-attributes-listing/dossier-attributes-listing-screen.component.html +++ b/apps/red-ui/src/app/modules/admin/screens/dossier-attributes-listing/dossier-attributes-listing-screen.component.html @@ -43,7 +43,7 @@
diff --git a/apps/red-ui/src/app/modules/admin/screens/dossier-attributes-listing/dossier-attributes-listing-screen.component.ts b/apps/red-ui/src/app/modules/admin/screens/dossier-attributes-listing/dossier-attributes-listing-screen.component.ts index 63dbde699..5fc724ce7 100644 --- a/apps/red-ui/src/app/modules/admin/screens/dossier-attributes-listing/dossier-attributes-listing-screen.component.ts +++ b/apps/red-ui/src/app/modules/admin/screens/dossier-attributes-listing/dossier-attributes-listing-screen.component.ts @@ -28,9 +28,8 @@ export class DossierAttributesListingScreenComponent extends BaseListingComponen readonly permissionsService: PermissionsService ) { super(_injector); - this._searchService.setSearchKey('label'); - this._screenStateService.setIdKey('id'); - this._sortingService.setScreenName(ScreenNames.DOSSIER_ATTRIBUTES_LISTING); + this.searchService.setSearchKey('label'); + this.sortingService.setScreenName(ScreenNames.DOSSIER_ATTRIBUTES_LISTING); _appStateService.activateDossierTemplate(_activatedRoute.snapshot.params.dossierTemplateId); } @@ -41,7 +40,7 @@ export class DossierAttributesListingScreenComponent extends BaseListingComponen openConfirmDeleteAttributeDialog($event: MouseEvent, dossierAttribute?: DossierAttributeConfig) { this._dialogService.openDialog('confirm', $event, null, async () => { this._loadingService.start(); - const ids = dossierAttribute ? [dossierAttribute.id] : this._screenStateService.selectedEntitiesIds; + const ids = dossierAttribute ? [dossierAttribute.id] : this.screenStateService.selectedEntities.map(item => item.id); await this._dossierAttributesService.deleteConfigs(ids); await this._loadData(); }); @@ -61,7 +60,7 @@ export class DossierAttributesListingScreenComponent extends BaseListingComponen private async _loadData() { this._loadingService.start(); const attributes = await this._dossierAttributesService.getConfig(); - this._screenStateService.setEntities(attributes); + this.screenStateService.setEntities(attributes); this.filterService.filterEntities(); this._loadingService.stop(); } diff --git a/apps/red-ui/src/app/modules/admin/screens/dossier-template-listing/dossier-templates-listing-screen.component.html b/apps/red-ui/src/app/modules/admin/screens/dossier-template-listing/dossier-templates-listing-screen.component.html index a2649fbed..1afdd7a3a 100644 --- a/apps/red-ui/src/app/modules/admin/screens/dossier-template-listing/dossier-templates-listing-screen.component.html +++ b/apps/red-ui/src/app/modules/admin/screens/dossier-template-listing/dossier-templates-listing-screen.component.html @@ -43,7 +43,7 @@
@@ -58,7 +58,7 @@
-
+
{ this._loadingService.start(); - await this._dossierTemplateControllerService.deleteDossierTemplates(this._screenStateService.selectedEntitiesIds).toPromise(); - this._screenStateService.setSelectedEntitiesIds([]); + await this._dossierTemplateControllerService + .deleteDossierTemplates(this.screenStateService.selectedEntities.map(d => d.dossierTemplateId)) + .toPromise(); + this.screenStateService.setSelectedEntities([]); await this._appStateService.loadAllDossierTemplates(); await this._appStateService.loadDictionaryData(); this.loadDossierTemplatesData(); @@ -52,7 +53,7 @@ export class DossierTemplatesListingScreenComponent extends BaseListingComponent loadDossierTemplatesData() { this._loadingService.start(); this._appStateService.reset(); - this._screenStateService.setEntities(this._appStateService.dossierTemplates); + this.screenStateService.setEntities(this._appStateService.dossierTemplates); this.filterService.filterEntities(); this._loadDossierTemplateStats(); this._loadingService.stop(); @@ -67,7 +68,7 @@ export class DossierTemplatesListingScreenComponent extends BaseListingComponent } private _loadDossierTemplateStats() { - this._screenStateService.entities.forEach(rs => { + this.screenStateService.allEntities.forEach(rs => { const dictionaries = this._appStateService.dictionaryData[rs.dossierTemplateId]; if (dictionaries) { rs.dictionariesCount = Object.keys(dictionaries) 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 ab90c8b99..599bf6e44 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 @@ -44,7 +44,7 @@
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 f66cf50a0..9a65a16ff 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 @@ -31,9 +31,8 @@ export class FileAttributesListingScreenComponent extends BaseListingComponent f.id), + this._appStateService.activeDossierTemplateId + ) .toPromise(); } await this._loadData(); @@ -95,7 +97,7 @@ export class FileAttributesListingScreenComponent extends BaseListingComponent this._isNew(entry)) - .map(entry => this._makeDecorationFor(entry)); + const newDecorations = this.currentLines.filter(entry => this._isNew(entry)).map(entry => this._makeDecorationFor(entry)); this._decorations = this._codeEditor.deltaDecorations(this._decorations, newDecorations); } @@ -153,14 +149,12 @@ export class RulesScreenComponent extends ComponentHasChanges { } private _initialize() { - this._rulesControllerService - .downloadRules(this._appStateService.activeDossierTemplateId) - .subscribe( - rules => { - this.currentLines = this.initialLines = rules.rules.split('\n'); - this.revert(); - }, - () => (this.processing = false) - ); + this._rulesControllerService.downloadRules(this._appStateService.activeDossierTemplateId).subscribe( + rules => { + this.currentLines = this.initialLines = rules.rules.split('\n'); + this.revert(); + }, + () => (this.processing = false) + ); } } diff --git a/apps/red-ui/src/app/modules/admin/screens/smtp-config/smtp-config-screen.component.ts b/apps/red-ui/src/app/modules/admin/screens/smtp-config/smtp-config-screen.component.ts index a5d70e5b4..b36206694 100644 --- a/apps/red-ui/src/app/modules/admin/screens/smtp-config/smtp-config-screen.component.ts +++ b/apps/red-ui/src/app/modules/admin/screens/smtp-config/smtp-config-screen.component.ts @@ -79,51 +79,37 @@ export class SmtpConfigScreenComponent implements OnInit { async ngOnInit() { await this._loadData(); - this.generalSettings = await this._generalSettingsControllerService - .getGeneralConfigurations() - .toPromise(); + this.generalSettings = await this._generalSettingsControllerService.getGeneralConfigurations().toPromise(); this._initialGeneralSettings = Object.assign({}, this.generalSettings); } async save() { this.viewReady = false; - await this._smtpConfigService - .updateSMTPConfiguration(this.configForm.getRawValue()) - .toPromise(); + await this._smtpConfigService.updateSMTPConfiguration(this.configForm.getRawValue()).toPromise(); this._initialValue = this.configForm.getRawValue(); this.viewReady = true; } async saveGeneralConfig() { this.viewReady = false; - await this._generalSettingsControllerService - .updateGeneralConfigurations(this.generalSettings) - .toPromise(); + await this._generalSettingsControllerService.updateGeneralConfigurations(this.generalSettings).toPromise(); this.viewReady = true; } openAuthConfigDialog(skipDisableOnCancel?: boolean) { - this._dialogService.openDialog( - 'smtpAuthConfig', - null, - this.configForm.getRawValue(), - null, - authConfig => { - if (authConfig) { - this.configForm.patchValue(authConfig); - } else if (!skipDisableOnCancel) { - this.configForm.patchValue({ auth: false }, { emitEvent: false }); - } + this._dialogService.openDialog('smtpAuthConfig', null, this.configForm.getRawValue(), null, authConfig => { + if (authConfig) { + this.configForm.patchValue(authConfig); + } else if (!skipDisableOnCancel) { + this.configForm.patchValue({ auth: false }, { emitEvent: false }); } - ); + }); } async testConnection() { this.viewReady = false; try { - await this._smtpConfigService - .testSMTPConfiguration(this.configForm.getRawValue()) - .toPromise(); + await this._smtpConfigService.testSMTPConfiguration(this.configForm.getRawValue()).toPromise(); this._notificationService.showToastNotification( this._translateService.instant('smtp-config-screen.test.success'), undefined, @@ -142,9 +128,7 @@ export class SmtpConfigScreenComponent implements OnInit { private async _loadData() { try { - this._initialValue = await this._smtpConfigService - .getCurrentSMTPConfiguration() - .toPromise(); + this._initialValue = await this._smtpConfigService.getCurrentSMTPConfiguration().toPromise(); this.configForm.patchValue(this._initialValue, { emitEvent: false }); } catch (e) { } finally { 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 259e0f8b6..b7f469d6c 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 @@ -32,8 +32,7 @@ export class TrashScreenComponent extends BaseListingComponent implemen private readonly _appConfigService: AppConfigService ) { super(_injector); - this._sortingService.setScreenName(ScreenNames.DOSSIER_LISTING); - this._screenStateService.setIdKey('dossierId'); + this.sortingService.setScreenName(ScreenNames.DOSSIER_LISTING); } async ngOnInit(): Promise { @@ -46,7 +45,7 @@ export class TrashScreenComponent extends BaseListingComponent implemen } async loadDossierTemplatesData(): Promise { - this._screenStateService.setEntities(await this._dossiersService.getDeletedDossiers()); + this.screenStateService.setEntities(await this._dossiersService.getDeleted()); } getRestoreDate(softDeletedTime: string): string { @@ -57,11 +56,11 @@ export class TrashScreenComponent extends BaseListingComponent implemen return dossier.dossierId; } - bulkDelete(dossierIds = this._screenStateService.selectedEntitiesIds) { + bulkDelete(dossierIds = this.screenStateService.selectedEntities.map(d => d.dossierId)) { this._loadingService.loadWhile(this._hardDelete(dossierIds)); } - bulkRestore(dossierIds = this._screenStateService.selectedEntitiesIds) { + bulkRestore(dossierIds = this.screenStateService.selectedEntities.map(d => d.dossierId)) { this._loadingService.loadWhile(this._restore(dossierIds)); } @@ -76,9 +75,9 @@ export class TrashScreenComponent extends BaseListingComponent implemen } private _removeFromList(ids: string[]): void { - const entities = this._screenStateService.entities.filter(e => !ids.includes(e.dossierId)); - this._screenStateService.setEntities(entities); - this._screenStateService.setSelectedEntitiesIds([]); + const entities = this.screenStateService.allEntities.filter(e => !ids.includes(e.dossierId)); + this.screenStateService.setEntities(entities); + this.screenStateService.setSelectedEntities([]); this.filterService.filterEntities(); } } 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 f83253c24..d1b4aca33 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 @@ -9,7 +9,7 @@
@@ -38,28 +38,19 @@
- {{ - 'user-listing.table-header.title' - | translate: { length: (displayedEntities$ | async)?.length } - }} + {{ 'user-listing.table-header.title' | translate: { length: (displayedEntities$ | async)?.length } }}
- + - + - + - +
@@ -98,14 +80,9 @@ -
+
- +
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 8b7edbee3..e462094ab 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 @@ -39,12 +39,11 @@ export class UserListingScreenComponent extends BaseListingComponent imple protected readonly _injector: Injector ) { super(_injector); - this._screenStateService.setIdKey('userId'); } get canDeleteSelected$(): Observable { - const entities$ = this._screenStateService.selectedEntitiesIds$; - return entities$.pipe(map(all => all.indexOf(this.userService.userId) === -1)); + const entities$ = this.screenStateService.selectedEntities$; + return entities$.pipe(map(all => all.indexOf(this.userService.user) === -1)); } async ngOnInit() { @@ -79,7 +78,7 @@ export class UserListingScreenComponent extends BaseListingComponent imple } bulkDelete() { - this.openDeleteUsersDialog(this._screenStateService.entities.filter(u => this.isSelected(u))); + this.openDeleteUsersDialog(this.screenStateService.allEntities.filter(u => this.isSelected(u))); } trackById(index: number, user: User) { @@ -87,7 +86,7 @@ export class UserListingScreenComponent extends BaseListingComponent imple } private async _loadData() { - this._screenStateService.setEntities(await this._userControllerService.getAllUsers().toPromise()); + this.screenStateService.setEntities(await this._userControllerService.getAllUsers().toPromise()); await this.userService.loadAllUsers(); this.filterService.filterEntities(); this._computeStats(); diff --git a/apps/red-ui/src/app/modules/dossier/components/bulk-actions/dossier-overview-bulk-actions.component.ts b/apps/red-ui/src/app/modules/dossier/components/bulk-actions/dossier-overview-bulk-actions.component.ts index 47f2dfe2b..9e3f0584c 100644 --- a/apps/red-ui/src/app/modules/dossier/components/bulk-actions/dossier-overview-bulk-actions.component.ts +++ b/apps/red-ui/src/app/modules/dossier/components/bulk-actions/dossier-overview-bulk-actions.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectorRef, Component, EventEmitter, Input, Output } from '@angular/core'; +import { ChangeDetectorRef, Component, EventEmitter, Output } from '@angular/core'; import { AppStateService } from '@state/app-state.service'; import { UserService } from '@services/user.service'; import { FileManagementControllerService, ReanalysisControllerService } from '@redaction/red-ui-http'; @@ -8,8 +8,9 @@ import { FileActionService } from '../../services/file-action.service'; import { from, Observable } from 'rxjs'; import { StatusOverlayService } from '@upload-download/services/status-overlay.service'; import { DossiersDialogService } from '../../services/dossiers-dialog.service'; -import { LoadingService } from '@services/loading.service'; -import { ConfirmationDialogInput } from '@shared/dialogs/confirmation-dialog/confirmation-dialog.component'; +import { LoadingService } from '../../../../services/loading.service'; +import { ConfirmationDialogInput } from '../../../shared/dialogs/confirmation-dialog/confirmation-dialog.component'; +import { ScreenStateService } from '../../../shared/services/screen-state.service'; @Component({ selector: 'redaction-dossier-overview-bulk-actions', @@ -17,8 +18,6 @@ import { ConfirmationDialogInput } from '@shared/dialogs/confirmation-dialog/con styleUrls: ['./dossier-overview-bulk-actions.component.scss'] }) export class DossierOverviewBulkActionsComponent { - @Input() - selectedFileIds: string[]; @Output() reload = new EventEmitter(); @@ -32,7 +31,8 @@ export class DossierOverviewBulkActionsComponent { private readonly _fileActionService: FileActionService, private readonly _statusOverlayService: StatusOverlayService, private readonly _changeDetectorRef: ChangeDetectorRef, - private readonly _loadingService: LoadingService + private readonly _loadingService: LoadingService, + private readonly _screenStateService: ScreenStateService ) {} get dossier() { @@ -40,30 +40,27 @@ export class DossierOverviewBulkActionsComponent { } get selectedFiles(): FileStatusWrapper[] { - return this.selectedFileIds.map(fileId => - this._appStateService.getFileById(this._appStateService.activeDossier.dossier.dossierId, fileId) - ); + return this._screenStateService.selectedEntities; } get areAllFilesSelected() { return ( this._appStateService.activeDossier.files.length !== 0 && - this.selectedFileIds.length === this._appStateService.activeDossier.files.length + this.selectedFiles.length === this._appStateService.activeDossier.files.length ); } get areSomeFilesSelected() { - return this.selectedFileIds.length > 0; + return this.selectedFiles.length > 0; } get allSelectedFilesCanBeAssignedIntoSameState() { if (this.areSomeFilesSelected) { - const selectedFiles = this.selectedFiles; - const allFilesAreUnderReviewOrUnassigned = selectedFiles.reduce( + const allFilesAreUnderReviewOrUnassigned = this.selectedFiles.reduce( (acc, file) => acc && (file.isUnderReview || file.isUnassigned), true ); - const allFilesAreUnderApproval = selectedFiles.reduce((acc, file) => acc && file.isUnderApproval, true); + const allFilesAreUnderApproval = this.selectedFiles.reduce((acc, file) => acc && file.isUnderApproval, true); return allFilesAreUnderReviewOrUnassigned || allFilesAreUnderApproval; } return false; @@ -139,11 +136,14 @@ export class DossierOverviewBulkActionsComponent { async () => { this._loadingService.start(); await this._fileManagementControllerService - .deleteFiles(this.selectedFileIds, this._appStateService.activeDossierId) + .deleteFiles( + this.selectedFiles.map(item => item.fileId), + this._appStateService.activeDossierId + ) .toPromise(); await this._appStateService.reloadActiveDossierFiles(); this.reload.emit(); - this.selectedFileIds.splice(0, this.selectedFileIds.length); + this._screenStateService.setSelectedEntities([]); this._loadingService.stop(); } ); @@ -153,12 +153,9 @@ export class DossierOverviewBulkActionsComponent { // If more than 1 approver - show dialog and ask who to assign if (this._appStateService.activeDossier.approverIds.length > 1) { this._loadingService.start(); - const files = this.selectedFileIds.map(fileId => - this._appStateService.getFileById(this._appStateService.activeDossierId, fileId) - ); this._dialogService.openAssignFileToUserDialog( - files, + this.selectedFiles, 'approver', () => { this.reload.emit(); @@ -198,11 +195,10 @@ export class DossierOverviewBulkActionsComponent { assign() { this._loadingService.start(); - const files = this.selectedFileIds.map(fileId => this._appStateService.getFileById(this._appStateService.activeDossierId, fileId)); - const mode = files[0].isUnderApproval ? 'approver' : 'reviewer'; + const mode = this.selectedFiles[0].isUnderApproval ? 'approver' : 'reviewer'; - this._dialogService.openAssignFileToUserDialog(files, mode, () => { + this._dialogService.openAssignFileToUserDialog(this.selectedFiles, mode, () => { this.reload.emit(); this._loadingService.stop(); }); 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 da86fc640..95b943ae7 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 @@ -38,12 +38,10 @@
@@ -65,9 +63,9 @@ >
-
+
-
{{ appStateService.activeDossier.dossier.description }}
+
{{ description }}
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 3eba24b9d..39de289e9 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 @@ -18,6 +18,7 @@ import { DossierAttributeWithValue } from '@models/dossier-attributes.model'; styleUrls: ['./dossier-details.component.scss'] }) export class DossierDetailsComponent implements OnInit { + readonly i18nKey = 'dossier-overview.dossier-details.'; documentsChartData: DoughnutChartConfig[] = []; owner: User; editingOwner = false; @@ -71,7 +72,7 @@ export class DossierDetailsComponent implements OnInit { key: key }); } - this.documentsChartData.sort((a, b) => StatusSorter[a.key] - StatusSorter[b.key]); + this.documentsChartData.sort(StatusSorter.byKey); this.documentsChartData = this.translateChartService.translateStatus(this.documentsChartData); this._changeDetectorRef.detectChanges(); } @@ -80,7 +81,7 @@ export class DossierDetailsComponent implements OnInit { this.owner = typeof user === 'string' ? this._userService.getRedUserById(user) : user; const dw = Object.assign({}, this.appStateService.activeDossier); dw.dossier.ownerId = this.owner.userId; - await this.appStateService.addOrUpdateDossier(dw.dossier); + await this.appStateService.createOrUpdateDossier(dw.dossier); const ownerName = this._userService.getNameForId(this.owner.userId); const dossierName = this.appStateService.activeDossier.name; 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 1bb665327..6609a95ad 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 @@ -27,7 +27,6 @@
- this.userService - .getNameForId(user.userId) - .toLowerCase() - .includes(searchQuery.toLowerCase()) - ) + .filter(user => this.userService.getNameForId(user.userId).toLowerCase().includes(searchQuery.toLowerCase())) .filter(user => this.selectedOwnerId !== user.userId) .map(user => user.userId); } @@ -76,10 +71,7 @@ export class TeamMembersManagerComponent implements OnInit { const initialApprovers = this.dossierWrapper.approverIds.sort(); const currentApprovers = this.selectedApproversList.sort(); - return ( - this._compareLists(initialMembers, currentMembers) || - this._compareLists(initialApprovers, currentApprovers) - ); + return this._compareLists(initialMembers, currentMembers) || this._compareLists(initialApprovers, currentApprovers); } isOwner(userId: string): boolean { @@ -96,7 +88,7 @@ export class TeamMembersManagerComponent implements OnInit { dw.dossier.memberIds = memberIds; dw.dossier.approverIds = approverIds; dw.dossier.ownerId = ownerId; - result = await this._appStateService.addOrUpdateDossier(dw.dossier); + result = await this._appStateService.createOrUpdateDossier(dw.dossier); this.save.emit(result); } catch (error) { this._notificationService.showToastNotification( diff --git a/apps/red-ui/src/app/modules/dossier/dialogs/add-dossier-dialog/add-dossier-dialog.component.ts b/apps/red-ui/src/app/modules/dossier/dialogs/add-dossier-dialog/add-dossier-dialog.component.ts index f00e4e4bb..a0cf0b415 100644 --- a/apps/red-ui/src/app/modules/dossier/dialogs/add-dossier-dialog/add-dossier-dialog.component.ts +++ b/apps/red-ui/src/app/modules/dossier/dialogs/add-dossier-dialog/add-dossier-dialog.component.ts @@ -35,10 +35,7 @@ export class AddDossierDialogComponent { }, { validators: control => - control.value.reportTypes?.length > 0 || - control.value.downloadFileTypes?.length > 0 - ? null - : { downloadPackage: true } + control.value.reportTypes?.length > 0 || control.value.downloadFileTypes?.length > 0 ? null : { downloadPackage: true } } ); } @@ -62,14 +59,12 @@ export class AddDossierDialogComponent { async saveDossier() { const dossier: Dossier = this._formToObject(); - const foundDossier = this._appStateService.allDossiers.find( - p => p.dossier.dossierId === dossier.dossierId - ); + const foundDossier = this._appStateService.allDossiers.find(p => p.dossier.dossierId === dossier.dossierId); if (foundDossier) { dossier.memberIds = foundDossier.memberIds; } - const savedDossier = await this._appStateService.addOrUpdateDossier(dossier); + const savedDossier = await this._appStateService.createOrUpdateDossier(dossier); if (savedDossier) { this.dialogRef.close({ dossier: savedDossier }); } @@ -77,7 +72,7 @@ export class AddDossierDialogComponent { async saveDossierAndAddMembers() { const dossier: Dossier = this._formToObject(); - const savedDossier = await this._appStateService.addOrUpdateDossier(dossier); + const savedDossier = await this._appStateService.createOrUpdateDossier(dossier); if (savedDossier) { this.dialogRef.close({ addMembers: true, dossier: savedDossier }); } @@ -85,9 +80,7 @@ export class AddDossierDialogComponent { dossierTemplateChanged(dossierTemplateId) { // get current selected dossierTemplate - const dossierTemplate = this.dossierTemplates.find( - r => r.dossierTemplateId === dossierTemplateId - ); + const dossierTemplate = this.dossierTemplates.find(r => r.dossierTemplateId === dossierTemplateId); if (dossierTemplate) { // update dropdown values this.dossierForm.patchValue( diff --git a/apps/red-ui/src/app/modules/dossier/dialogs/assign-reviewer-approver-dialog/assign-reviewer-approver-dialog.component.ts b/apps/red-ui/src/app/modules/dossier/dialogs/assign-reviewer-approver-dialog/assign-reviewer-approver-dialog.component.ts index 362872dd8..0c7390409 100644 --- a/apps/red-ui/src/app/modules/dossier/dialogs/assign-reviewer-approver-dialog/assign-reviewer-approver-dialog.component.ts +++ b/apps/red-ui/src/app/modules/dossier/dialogs/assign-reviewer-approver-dialog/assign-reviewer-approver-dialog.component.ts @@ -111,15 +111,9 @@ export class AssignReviewerApproverDialogComponent { uniqueReviewers.add(file.currentReviewer); } } - let singleUser = - uniqueReviewers.size === 1 - ? uniqueReviewers.values().next().value - : this.userService.userId; + let singleUser = uniqueReviewers.size === 1 ? uniqueReviewers.values().next().value : this.userService.userId; - singleUser = - this.singleUsersSelectOptions.indexOf(singleUser) >= 0 - ? singleUser - : this.singleUsersSelectOptions[0]; + singleUser = this.singleUsersSelectOptions.indexOf(singleUser) >= 0 ? singleUser : this.singleUsersSelectOptions[0]; this.usersForm = this._formBuilder.group({ singleUser: [singleUser, Validators.required] diff --git a/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/download-package/edit-dossier-download-package.component.ts b/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/download-package/edit-dossier-download-package.component.ts index c00e79dab..b336ede21 100644 --- a/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/download-package/edit-dossier-download-package.component.ts +++ b/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/download-package/edit-dossier-download-package.component.ts @@ -71,7 +71,7 @@ export class EditDossierDownloadPackageComponent implements OnInit, EditDossierS downloadFileTypes: this.dossierForm.get('downloadFileTypes').value, reportTypes: this.dossierForm.get('reportTypes').value }; - const updatedDossier = await this._appStateService.addOrUpdateDossier(dossier); + const updatedDossier = await this._appStateService.createOrUpdateDossier(dossier); this.updateDossier.emit(updatedDossier); } diff --git a/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/general-info/edit-dossier-general-info.component.ts b/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/general-info/edit-dossier-general-info.component.ts index 3435b47be..3fd26d42d 100644 --- a/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/general-info/edit-dossier-general-info.component.ts +++ b/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/general-info/edit-dossier-general-info.component.ts @@ -10,7 +10,7 @@ import { PermissionsService } from '@services/permissions.service'; import { Router } from '@angular/router'; import { MatDialogRef } from '@angular/material/dialog'; import { EditDossierDialogComponent } from '../edit-dossier-dialog.component'; -import { NotificationService, NotificationType } from '@services/notification.service'; +import { NotificationService, NotificationType } from '../../../../../services/notification.service'; import { TranslateService } from '@ngx-translate/core'; @Component({ @@ -100,7 +100,7 @@ export class EditDossierGeneralInfoComponent implements OnInit, EditDossierSecti dueDate: this.hasDueDate ? this.dossierForm.get('dueDate').value : undefined, dossierTemplateId: this.dossierForm.get('dossierTemplateId').value }; - const updatedDossier = await this._appStateService.addOrUpdateDossier(dossier); + const updatedDossier = await this._appStateService.createOrUpdateDossier(dossier); if (updatedDossier) this.updateDossier.emit(updatedDossier); } 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 2b1720166..a0b321d23 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,8 +7,8 @@ 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 { Subscription, timer } from 'rxjs'; -import { filter, tap } from 'rxjs/operators'; +import { timer } from 'rxjs'; +import { filter } 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'; @@ -30,12 +30,15 @@ import { ScreenStateService } from '@shared/services/screen-state.service'; import { BaseListingComponent } from '@shared/base/base-listing.component'; import { ScreenNames, SortingService } from '@services/sorting.service'; +const isLeavingScreen = event => event instanceof NavigationStart && event.url !== '/main/dossiers'; + @Component({ templateUrl: './dossier-listing-screen.component.html', styleUrls: ['./dossier-listing-screen.component.scss'], providers: [FilterService, SearchService, ScreenStateService, SortingService] }) export class DossierListingScreenComponent extends BaseListingComponent implements OnInit, OnDestroy, OnAttach, OnDetach { + readonly itemSize = 95; dossiersChartData: DoughnutChartConfig[] = []; documentsChartData: DoughnutChartConfig[] = []; buttonConfigs: ButtonConfig[] = [ @@ -48,12 +51,7 @@ export class DossierListingScreenComponent extends BaseListingComponent; @@ -70,65 +68,43 @@ export class DossierListingScreenComponent extends BaseListingComponent p.dossier.status === Dossier.StatusEnum.ACTIVE).length; - } - - private get _inactiveDossiersCount(): number { - return this._screenStateService.entities.length - this._activeDossiersCount; - } - ngOnInit(): void { 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.addSubscription = timer(0, 10000).subscribe(async () => { + await this._appStateService.loadAllDossiers(); + this._loadEntitiesFromState(); this.calculateData(); }); - this._routerEventsScrollPositionSub = this._router.events - .pipe(filter(event => event instanceof NavigationStart)) - .subscribe((event: NavigationStart) => { - if (event.url !== '/main/dossiers') { - this._lastScrollPosition = this.scrollViewport.measureScrollOffset('top'); - } - }); + this.addSubscription = this._appStateService.fileChanged.subscribe(() => { + this.calculateData(); + }); + + this.addSubscription = this._router.events.pipe(filter(isLeavingScreen)).subscribe(() => { + this._lastScrollPosition = this.scrollViewport.measureScrollOffset('top'); + }); } ngOnAttach() { + this.scrollViewport.scrollTo({ top: this._lastScrollPosition }); this._appStateService.reset(); this._loadEntitiesFromState(); this.ngOnInit(); - this.scrollViewport.scrollTo({ top: this._lastScrollPosition }); } - ngOnDetach() { + ngOnDetach(): void { this.ngOnDestroy(); } ngOnDestroy(): void { - this._dossierAutoUpdateTimer.unsubscribe(); - this._routerEventsScrollPositionSub.unsubscribe(); - this._fileChangedSub.unsubscribe(); + super.ngOnDestroy(); } getDossierTemplate(dw: DossierWrapper): DossierTemplateModel { @@ -147,6 +123,18 @@ export class DossierListingScreenComponent extends BaseListingComponent p.dossier.status === Dossier.StatusEnum.ACTIVE).length; + } + + private get _inactiveDossiersCount(): number { + return this.screenStateService.allEntities.length - this._activeDossiersCount; + } + calculateData() { this._computeAllFilters(); @@ -165,12 +153,12 @@ export class DossierListingScreenComponent extends BaseListingComponent StatusSorter[a.key] - StatusSorter[b.key]); + this.documentsChartData.sort(StatusSorter.byKey); this.documentsChartData = this._translateChartService.translateStatus(this.documentsChartData); } private _loadEntitiesFromState() { - this._screenStateService.setEntities(this._appStateService.allDossiers); + this.screenStateService.setEntities(this._appStateService.allDossiers); } private _computeAllFilters() { @@ -179,7 +167,7 @@ export class DossierListingScreenComponent extends BaseListingComponent(); const allDistinctDossierTemplates = new Set(); - this._screenStateService?.entities?.forEach(entry => { + this.screenStateService?.allEntities?.forEach(entry => { // all people entry.dossier.memberIds.forEach(f => allDistinctPeople.add(f)); // Needs work @@ -267,22 +255,22 @@ export class DossierListingScreenComponent extends BaseListingComponent dw.ownerId === this._user.id + checker: (dw: DossierWrapper) => dw.ownerId === this._userId }, { key: 'to-approve', 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._userId) }, { key: 'to-review', 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._userId) }, { key: 'other', 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._userId) } ]; 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 a80cc7fc4..e7d1c42b5 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 @@ -49,10 +49,7 @@ {{ 'dossier-overview.table-header.title' | translate: { length: (displayedEntities$ | async)?.length || 0 } }} - +
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 3ee41e805..88c1161d3 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 @@ -12,9 +12,8 @@ import { DossierDetailsComponent } from '../../components/dossier-details/dossie import { FileStatusWrapper } from '@models/file/file-status.wrapper'; import { PermissionsService } from '@services/permissions.service'; import { UserService } from '@services/user.service'; -import { FileStatus, UserPreferenceControllerService } from '@redaction/red-ui-http'; -import { Subscription, timer } from 'rxjs'; -import { filter, tap } from 'rxjs/operators'; +import { timer } from 'rxjs'; +import { filter } from 'rxjs/operators'; import { RedactionFilterSorter } from '@utils/sorters/redaction-filter-sorter'; import { StatusSorter } from '@utils/sorters/status-sorter'; import { convertFiles, handleFileDrop } from '@utils/file-drop-utils'; @@ -33,6 +32,7 @@ import { BaseListingComponent } from '@shared/base/base-listing.component'; import { LoadingService } from '@services/loading.service'; import { DossierAttributesService } from '@shared/services/controller-wrappers/dossier-attributes.service'; import { DossierAttributeWithValue } from '@models/dossier-attributes.model'; +import { UserPreferenceService } from '../../../../services/user-preference.service'; @Component({ templateUrl: './dossier-overview-screen.component.html', @@ -49,11 +49,9 @@ export class DossierOverviewScreenComponent dossierAttributes: DossierAttributeWithValue[] = []; @ViewChild(DossierDetailsComponent, { static: false }) private readonly _dossierDetailsComponent: DossierDetailsComponent; - private _filesAutoUpdateTimer: Subscription; - private _routerEventsScrollPositionSub: Subscription; - private _fileChangedSub: Subscription; private _lastScrollPosition: number; - private _lastOpenedFileId = ''; + private _lastOpenedFileKey = 'Dossier-Recent-' + this.activeDossier.dossierId; + @ViewChild('needsWorkTemplate', { read: TemplateRef, static: true }) private readonly _needsWorkTemplate: TemplateRef; @ViewChild('fileInput') private _fileInput: ElementRef; @@ -69,7 +67,7 @@ export class DossierOverviewScreenComponent private readonly _translateService: TranslateService, private readonly _fileDropOverlayService: FileDropOverlayService, private readonly _appStateService: AppStateService, - private readonly _userPreferenceControllerService: UserPreferenceControllerService, + private readonly _userPreferenceService: UserPreferenceService, private readonly _appConfigService: AppConfigService, private readonly _changeDetectorRef: ChangeDetectorRef, private readonly _loadingService: LoadingService, @@ -77,9 +75,8 @@ export class DossierOverviewScreenComponent protected readonly _injector: Injector ) { super(_injector); - this._sortingService.setScreenName(ScreenNames.DOSSIER_OVERVIEW); - this._searchService.setSearchKey('searchField'); - this._screenStateService.setIdKey('fileId'); + this.sortingService.setScreenName(ScreenNames.DOSSIER_OVERVIEW); + this.searchService.setSearchKey('searchField'); this._loadEntitiesFromState(); } @@ -99,30 +96,26 @@ export class DossierOverviewScreenComponent return this.filterService.getFilter('quickFilters')?.values.filter(f => !f.required && f.checked); } - isLastOpenedFile(fileStatus: FileStatusWrapper): boolean { - return this._lastOpenedFileId === fileStatus.fileId; + isLastOpenedFile({ fileId }: FileStatusWrapper): boolean { + return this._userPreferenceService.getLastOpenedFileId(this._lastOpenedFileKey) === fileId; } - async ngOnInit() { + async ngOnInit(): Promise { this._fileDropOverlayService.initFileDropHandling(); this.calculateData(); - this._filesAutoUpdateTimer = timer(0, 7500) - .pipe( - tap(async () => { - await this._appStateService.reloadActiveDossierFilesIfNecessary(); - this._loadEntitiesFromState(); - }) - ) - .subscribe(); + this.addSubscription = timer(0, 7500).subscribe(async () => { + await this._appStateService.reloadActiveDossierFilesIfNecessary(); + this._loadEntitiesFromState(); + }); - this._fileChangedSub = this._appStateService.fileChanged.subscribe(() => { + this.addSubscription = this._appStateService.fileChanged.subscribe(() => { this.calculateData(); }); - this._routerEventsScrollPositionSub = this._router.events - .pipe(filter(events => events instanceof NavigationStart)) + this.addSubscription = this._router.events + .pipe(filter(event => event instanceof NavigationStart)) .subscribe((event: NavigationStart) => { if (!event.url.endsWith(this._appStateService.activeDossierId)) { this._lastScrollPosition = this.scrollViewport.measureScrollOffset('top'); @@ -131,13 +124,6 @@ export class DossierOverviewScreenComponent this._loadingService.start(); - const userAttributes = await this._userPreferenceControllerService.getAllUserAttributes(); - if (userAttributes === null || userAttributes === undefined) return; - const key = 'Dossier-Recent-' + this.activeDossier.dossierId; - if (userAttributes[key]?.length > 0) { - this._lastOpenedFileId = userAttributes[key][0]; - } - this.dossierAttributes = await this._dossierAttributesService.getValues(this.activeDossier); this._loadingService.stop(); @@ -145,9 +131,7 @@ export class DossierOverviewScreenComponent ngOnDestroy(): void { this._fileDropOverlayService.cleanupFileDropHandling(); - this._filesAutoUpdateTimer.unsubscribe(); - this._fileChangedSub.unsubscribe(); - this._routerEventsScrollPositionSub.unsubscribe(); + super.ngOnDestroy(); } async ngOnAttach() { @@ -180,18 +164,8 @@ export class DossierOverviewScreenComponent }); } - isError(fileStatusWrapper: FileStatusWrapper) { - return fileStatusWrapper.status === FileStatus.StatusEnum.ERROR; - } - - isProcessing(fileStatusWrapper: FileStatusWrapper) { - return [FileStatus.StatusEnum.REPROCESS, FileStatus.StatusEnum.FULLREPROCESS, FileStatus.StatusEnum.PROCESSING].includes( - fileStatusWrapper.status - ); - } - reloadDossiers() { - this._appStateService.getFiles(this._appStateService.activeDossier, false).then(() => { + this._appStateService.getFiles(this.activeDossier, false).then(() => { this.calculateData(); }); } @@ -235,7 +209,7 @@ export class DossierOverviewScreenComponent } bulkActionPerformed() { - this._screenStateService.selectedEntitiesIds$.next([]); + this.screenStateService.setSelectedEntities([]); this.reloadDossiers(); } @@ -271,15 +245,12 @@ export class DossierOverviewScreenComponent moment(file.lastUpdated).add(this._appConfigService.getConfig(AppConfigKey.RECENT_PERIOD_IN_HOURS), 'hours').isAfter(moment()); private _loadEntitiesFromState() { - if (this.activeDossier) this._screenStateService.setEntities(this.activeDossier.files); + if (this.activeDossier) this.screenStateService.setEntities(this.activeDossier.files); } private async _uploadFiles(files: FileUploadModel[]) { const fileCount = await this._fileUploadService.uploadFiles(files); - if (fileCount) { - this._statusOverlayService.openUploadStatusOverlay(); - } - // this._changeDetectorRef.detectChanges(); + if (fileCount) this._statusOverlayService.openUploadStatusOverlay(); } private _computeAllFilters() { @@ -290,7 +261,7 @@ export class DossierOverviewScreenComponent const allDistinctAddedDates = new Set(); const allDistinctNeedsWork = new Set(); - this._screenStateService.entities.forEach(file => { + this.screenStateService.allEntities.forEach(file => { allDistinctPeople.add(file.currentReviewer); allDistinctFileStatusWrapper.add(file.status); allDistinctAddedDates.add(moment(file.added).format('DD/MM/YYYY')); @@ -370,7 +341,7 @@ export class DossierOverviewScreenComponent private _createQuickFilters() { let quickFilters = []; - if (this._screenStateService.entities.filter(this.recentlyModifiedChecker).length > 0) { + if (this.screenStateService.allEntities.filter(this.recentlyModifiedChecker).length > 0) { const recentPeriod = this._appConfigService.getConfig(AppConfigKey.RECENT_PERIOD_IN_HOURS); quickFilters = [ { diff --git a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/file-preview-screen.component.ts b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/file-preview-screen.component.ts index 7dcf7a362..15c433e57 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/file-preview-screen.component.ts +++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/file-preview-screen.component.ts @@ -34,7 +34,7 @@ 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'; import { handleFilterDelta, processFilters } from '@shared/components/filters/popup-filter/utils/filter-utils'; -import { LoadingService } from '@services/loading.service'; +import { LoadingService } from '../../../../services/loading.service'; import { stampPDFPage } from '../../../../utils/page-stamper'; import { TranslateService } from '@ngx-translate/core'; 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 index df92dfe8f..1379a4b02 100644 --- a/apps/red-ui/src/app/modules/dossier/services/dossiers.service.ts +++ b/apps/red-ui/src/app/modules/dossier/services/dossiers.service.ts @@ -1,11 +1,25 @@ import { Injectable } from '@angular/core'; -import { DossierControllerService } from '@redaction/red-ui-http'; +import { Dossier, DossierControllerService } from '@redaction/red-ui-http'; -@Injectable() +@Injectable({ + providedIn: 'root' +}) export class DossiersService { constructor(private readonly _dossierControllerService: DossierControllerService) {} - getDeletedDossiers() { + createOrUpdate(dossier: Dossier): Promise { + return this._dossierControllerService.createOrUpdateDossier(dossier).toPromise(); + } + + delete(dossierId: string): Promise { + return this._dossierControllerService.deleteDossier(dossierId).toPromise(); + } + + getAll(): Promise { + return this._dossierControllerService.getDossiers().toPromise(); + } + + getDeleted(): Promise { return this._dossierControllerService.getDeletedDossiers().toPromise(); } diff --git a/apps/red-ui/src/app/modules/dossier/services/manual-annotation.service.ts b/apps/red-ui/src/app/modules/dossier/services/manual-annotation.service.ts index 990fd71c5..055e3670d 100644 --- a/apps/red-ui/src/app/modules/dossier/services/manual-annotation.service.ts +++ b/apps/red-ui/src/app/modules/dossier/services/manual-annotation.service.ts @@ -77,12 +77,7 @@ export class ManualAnnotationService { return obs.pipe( tap( () => this._notify(this._getMessage(mode)), - error => - this._notify( - this._getMessage(mode, modifyDictionary, true), - NotificationType.ERROR, - error - ) + error => this._notify(this._getMessage(mode, modifyDictionary, true), NotificationType.ERROR, error) ) ); } @@ -124,9 +119,7 @@ export class ManualAnnotationService { // /manualRedaction/redaction/legalBasisChange // /manualRedaction/request/legalBasis changeLegalBasis(annotationId: string, legalBasis: string, comment?: string) { - const mode: Mode = this._permissionsService.isApprover() - ? 'change-legal-basis' - : 'request-change-legal-basis'; + const mode: Mode = this._permissionsService.isApprover() ? 'change-legal-basis' : 'request-change-legal-basis'; return this._makeRequest(mode, { annotationId, legalBasis, comment }); } @@ -134,9 +127,7 @@ export class ManualAnnotationService { // /manualRedaction/redaction/recategorize // /manualRedaction/request/recategorize recategorizeImage(annotationId: string, type: string, comment: string) { - const mode: Mode = this._permissionsService.isApprover() - ? 'recategorize-image' - : 'request-image-recategorization'; + const mode: Mode = this._permissionsService.isApprover() ? 'recategorize-image' : 'request-image-recategorization'; return this._makeRequest(mode, { annotationId, type, comment }); } @@ -145,21 +136,14 @@ export class ManualAnnotationService { // /manualRedaction/request/add addAnnotation(manualRedactionEntry: AddRedactionRequest) { const mode: Mode = this._permissionsService.isApprover() ? 'add' : 'suggest'; - return this._makeRequest( - mode, - manualRedactionEntry, - null, - manualRedactionEntry.addToDictionary - ); + return this._makeRequest(mode, manualRedactionEntry, null, manualRedactionEntry.addToDictionary); } // this wraps // /manualRedaction/redaction/force // /manualRedaction/request/force forceRedaction(request: ForceRedactionRequest) { - const mode: Mode = this._permissionsService.isApprover() - ? 'force-redaction' - : 'request-force-redaction'; + const mode: Mode = this._permissionsService.isApprover() ? 'force-redaction' : 'request-force-redaction'; return this._makeRequest(mode, request); } @@ -167,21 +151,11 @@ export class ManualAnnotationService { // /manualRedaction/approve approveRequest(annotationId: string, addToDictionary: boolean = false) { // for only here - approve the request - return this._makeRequest( - 'approve', - { addOrRemoveFromDictionary: addToDictionary }, - annotationId, - addToDictionary - ); + return this._makeRequest('approve', { addOrRemoveFromDictionary: addToDictionary }, annotationId, addToDictionary); } undoRequest(annotationWrapper: AnnotationWrapper) { - return this._makeRequest( - 'undo', - annotationWrapper.id, - null, - annotationWrapper.isModifyDictionary - ); + return this._makeRequest('undo', annotationWrapper.id, null, annotationWrapper.isModifyDictionary); } // this wraps @@ -189,21 +163,13 @@ export class ManualAnnotationService { // /manualRedaction/undo declineOrRemoveRequest(annotationWrapper: AnnotationWrapper) { const mode: Mode = this._permissionsService.isApprover() ? 'decline' : 'undo'; - return this._makeRequest( - mode, - annotationWrapper.id, - null, - annotationWrapper.isModifyDictionary - ); + return this._makeRequest(mode, annotationWrapper.id, null, annotationWrapper.isModifyDictionary); } // this wraps // /manualRedaction/redaction/remove/ // /manualRedaction/request/remove/ - removeOrSuggestRemoveAnnotation( - annotationWrapper: AnnotationWrapper, - removeFromDictionary: boolean = false - ) { + removeOrSuggestRemoveAnnotation(annotationWrapper: AnnotationWrapper, removeFromDictionary: boolean = false) { let mode: Mode, body: any, removeDict = false; @@ -258,15 +224,10 @@ export class ManualAnnotationService { } private _notify(key: string, type: NotificationType = NotificationType.SUCCESS, data?: any) { - this._notificationService.showToastNotification( - this._translateService.instant(key, data), - null, - type, - { - positionClass: 'toast-file-preview', - actions: [] - } - ); + this._notificationService.showToastNotification(this._translateService.instant(key, data), null, type, { + positionClass: 'toast-file-preview', + actions: [] + }); } private _getMessage(mode: Mode, modifyDictionary?: boolean, error: boolean = false) { diff --git a/apps/red-ui/src/app/modules/shared/base/auto-unsubscribe.component.ts b/apps/red-ui/src/app/modules/shared/base/auto-unsubscribe.component.ts new file mode 100644 index 000000000..680769e31 --- /dev/null +++ b/apps/red-ui/src/app/modules/shared/base/auto-unsubscribe.component.ts @@ -0,0 +1,29 @@ +import { Component, OnDestroy } from '@angular/core'; +import { Subscription } from 'rxjs'; + +/** + * Inherit this class when you need to subscribe to observables in your components + * + * @remarks You must explicitly call super.ngOnDestroy in you component to unsubscribe + */ +@Component({ template: '' }) +export abstract class AutoUnsubscribeComponent implements OnDestroy { + private _subscriptions = new Subscription(); + + /** + * Call this method when you want to subscribe to an observable + * @param subscription - the new subscription to add to subscriptions array + */ + set addSubscription(subscription: Subscription) { + this._subscriptions.closed = false; + this._subscriptions.add(subscription); + } + + /** + * This method unsubscribes active subscriptions + * Explicitly call this method in your component's ngOnDestroy + */ + ngOnDestroy(): void { + this._subscriptions.unsubscribe(); + } +} 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 cdb5858b6..7ba2a27aa 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,4 +1,4 @@ -import { Component, Injector, ViewChild } from '@angular/core'; +import { Component, Injector, OnDestroy, ViewChild } from '@angular/core'; import { SortingOption, SortingService } from '@services/sorting.service'; import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling'; import { FilterService } from '../services/filter.service'; @@ -6,58 +6,60 @@ import { SearchService } from '../services/search.service'; import { ScreenStateService } from '../services/screen-state.service'; import { Observable } from 'rxjs'; import { FilterModel } from '../components/filters/popup-filter/model/filter.model'; +import { AutoUnsubscribeComponent } from './auto-unsubscribe.component'; @Component({ template: '' }) -export abstract class BaseListingComponent { +export abstract class BaseListingComponent extends AutoUnsubscribeComponent implements OnDestroy { @ViewChild(CdkVirtualScrollViewport) readonly scrollViewport: CdkVirtualScrollViewport; readonly filterService: FilterService; - protected readonly _sortingService: SortingService; - protected readonly _searchService: SearchService; - protected readonly _screenStateService: ScreenStateService; + readonly sortingService: SortingService; + readonly searchService: SearchService; + readonly screenStateService: ScreenStateService; protected constructor(protected readonly _injector: Injector) { + super(); this.filterService = this._injector.get>(FilterService); - this._sortingService = this._injector.get(SortingService); - this._searchService = this._injector.get>(SearchService); - this._screenStateService = this._injector.get>(ScreenStateService); + this.sortingService = this._injector.get(SortingService); + this.searchService = this._injector.get>(SearchService); + this.screenStateService = this._injector.get>(ScreenStateService); } - get selectedEntitiesIds$(): Observable { - return this._screenStateService.selectedEntitiesIds$; + ngOnDestroy(): void { + super.ngOnDestroy(); + } + + get selectedEntities$(): Observable { + return this.screenStateService.selectedEntities$; } get displayedEntities$(): Observable { - return this._screenStateService.displayedEntities$; + return this.screenStateService.displayedEntities$; } get allEntities$(): Observable { - return this._screenStateService.entities$; + return this.screenStateService.allEntities$; } get displayedEntities(): T[] { - return this._screenStateService.displayedEntities; + return this.screenStateService.displayedEntities; } get allEntities(): T[] { - return this._screenStateService.entities; + return this.screenStateService.allEntities; } get areAllEntitiesSelected() { - return this._screenStateService.areAllEntitiesSelected; + return this.screenStateService.areAllEntitiesSelected; } - get areSomeEntitiesSelected$() { - return this._screenStateService.areSomeEntitiesSelected$; + get areSomeEntitiesSelected$(): Observable { + return this.screenStateService.areSomeEntitiesSelected$; } get sortingOption(): SortingOption { - return this._sortingService.getSortingOption(); - } - - get searchForm() { - return this._searchService.searchForm; + return this.sortingService.getSortingOption(); } get noMatch(): boolean { @@ -68,28 +70,20 @@ export abstract class BaseListingComponent { return this.allEntities.length === 0; } - getFilter$(slug: string): Observable { - return this.filterService.getFilter$(slug); - } - - resetFilters() { - this.filterService.reset(); - } - toggleSort($event) { - this._sortingService.toggleSort($event); + this.sortingService.toggleSort($event); } toggleSelectAll() { - return this._screenStateService.toggleSelectAll(); + return this.screenStateService.toggleSelectAll(); } toggleEntitySelected(event: MouseEvent, entity: T) { event.stopPropagation(); - return this._screenStateService.toggleEntitySelected(entity); + return this.screenStateService.toggleEntitySelected(entity); } - isSelected(entity: T) { - return this._screenStateService.isSelected(entity); + isSelected(entity: T): boolean { + return this.screenStateService.isSelected(entity); } } diff --git a/apps/red-ui/src/app/modules/shared/components/buttons/file-download-btn/file-download-btn.component.ts b/apps/red-ui/src/app/modules/shared/components/buttons/file-download-btn/file-download-btn.component.ts index e36b401a7..3233c7e4e 100644 --- a/apps/red-ui/src/app/modules/shared/components/buttons/file-download-btn/file-download-btn.component.ts +++ b/apps/red-ui/src/app/modules/shared/components/buttons/file-download-btn/file-download-btn.component.ts @@ -36,25 +36,17 @@ export class FileDownloadBtnComponent { return this._permissionsService.canDownloadFiles(this.file); } - return ( - this.file.length > 0 && - this.file.reduce( - (acc, file) => acc && this._permissionsService.canDownloadFiles(file), - true - ) - ); + return this.file.length > 0 && this.file.reduce((acc, file) => acc && this._permissionsService.canDownloadFiles(file), true); } downloadFiles($event: MouseEvent) { $event.stopPropagation(); - this._fileDownloadService - .downloadFiles(Array.isArray(this.file) ? this.file : [this.file], this.dossier) - .subscribe(() => { - this._notificationService.showToastNotification( - this._translateService.instant('download-status.queued', { - baseUrl: this._baseHref - }) - ); - }); + this._fileDownloadService.downloadFiles(Array.isArray(this.file) ? this.file : [this.file], this.dossier).subscribe(() => { + this._notificationService.showToastNotification( + this._translateService.instant('download-status.queued', { + baseUrl: this._baseHref + }) + ); + }); } } diff --git a/apps/red-ui/src/app/modules/shared/components/simple-doughnut-chart/simple-doughnut-chart.component.html b/apps/red-ui/src/app/modules/shared/components/simple-doughnut-chart/simple-doughnut-chart.component.html index 577055adb..43f9c4c6c 100644 --- a/apps/red-ui/src/app/modules/shared/components/simple-doughnut-chart/simple-doughnut-chart.component.html +++ b/apps/red-ui/src/app/modules/shared/components/simple-doughnut-chart/simple-doughnut-chart.component.html @@ -8,7 +8,7 @@ > -
+
{{ displayedDataTotal }}
{{ subtitle | translate }}
@@ -37,7 +34,7 @@ (click)="selectValue(val.key)" *ngFor="let val of config" [class.active]="filterService.filterChecked$('statusFilters', val.key) | async" - [class.filter-disabled]="!filter" + [class.filter-disabled]="(filterService.getFilter$('statusFilters') | async)?.length === 0" > implements OnChanges { @Input() radius = 85; @Input() strokeWidth = 20; @Input() direction: 'row' | 'column' = 'column'; - @Input() filter: FilterModel[]; @Input() totalType: 'sum' | 'count' = 'sum'; @Input() counterText: string; - @Output() - toggleFilter = new EventEmitter(); chartData: any[] = []; perimeter: number; @@ -36,11 +32,11 @@ export class SimpleDoughnutChartComponent implements OnChanges { constructor(readonly filterService: FilterService) {} - get circumference() { + get circumference(): number { return 2 * Math.PI * this.radius; } - get dataTotal() { + get dataTotal(): number { return this.config.map(v => v.value).reduce((acc, val) => acc + val, 0); } @@ -56,48 +52,34 @@ export class SimpleDoughnutChartComponent implements OnChanges { } calculateChartData() { - const newData = []; let angleOffset = -90; - this.config.forEach(dataVal => { - if (dataVal.value > 0) { - const data = { - degrees: angleOffset - }; - newData.push(data); - angleOffset = this.dataPercentage(dataVal.value) * 360 + angleOffset; - } else { - newData.push(null); - } + this.chartData = this.config.map(dataVal => { + if (dataVal.value === 0) return null; + + const res = { degrees: angleOffset }; + angleOffset = this.dataPercentage(dataVal.value) * 360 + angleOffset; + return res; }); - this.chartData = newData; } - calculateStrokeDashOffset(dataVal) { + calculateStrokeDashOffset(dataVal: number): number { const strokeDiff = this.dataPercentage(dataVal) * this.circumference; return this.circumference - strokeDiff; } - dataPercentage(dataVal) { + dataPercentage(dataVal: number): number { return dataVal / this.dataTotal; } - returnCircleTransformValue(index) { + returnCircleTransformValue(index: number) { return `rotate(${this.chartData[index].degrees}, ${this.cx}, ${this.cy})`; } - getLabel(config: DoughnutChartConfig): string { - return this.totalType === 'sum' - ? `${config.value} ${config.label}` - : `${config.label} (${config.value} ${this.counterText})`; + getLabel({ label, value }: DoughnutChartConfig): string { + return this.totalType === 'sum' ? `${value} ${label}` : `${label} (${value} ${this.counterText})`; } - selectValue(key: string) { + selectValue(key: string): void { this.filterService.toggleFilter('statusFilters', key); - this.filterService.filterEntities(); - this.toggleFilter.emit(key); - } - - exists(index: number) { - return !!this.chartData[index]; } } 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 172f17ef4..b26f019ef 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 @@ -1,9 +1,6 @@ import { ChangeDetectorRef, Injectable } from '@angular/core'; import { FilterModel } from '@shared/components/filters/popup-filter/model/filter.model'; -import { - getFilteredEntities, - processFilters -} from '@shared/components/filters/popup-filter/utils/filter-utils'; +import { getFilteredEntities, processFilters } 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'; @@ -48,7 +45,7 @@ export class FilterService { } filterEntities(): void { - const filtered = getFilteredEntities(this._screenStateService.entities, this.filters); + const filtered = getFilteredEntities(this._screenStateService.allEntities, this.filters); this._screenStateService.setFilteredEntities(filtered); this._searchService.executeSearchImmediately(); @@ -98,8 +95,7 @@ export class FilterService { } private _toFlatFilters(entities: FilterWrapper[]): FilterModel[] { - const flatChildren = (filters: FilterModel[]) => - (filters ?? []).reduce((acc, f) => [...acc, ...(f?.filters ?? [])], []); + const flatChildren = (filters: FilterModel[]) => (filters ?? []).reduce((acc, f) => [...acc, ...(f?.filters ?? [])], []); return entities.reduce((acc, f) => [...acc, ...f.values, ...flatChildren(f.values)], []); } 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 6c6c0d86d..e8305e8d0 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 @@ -4,23 +4,21 @@ import { distinctUntilChanged, map } from 'rxjs/operators'; @Injectable() export class ScreenStateService { - entities$ = new BehaviorSubject([]); + allEntities$ = new BehaviorSubject([]); filteredEntities$ = new BehaviorSubject([]); displayedEntities$ = new BehaviorSubject([]); - selectedEntitiesIds$ = new BehaviorSubject([]); + selectedEntities$ = new BehaviorSubject([]); - private _idKey: string; - - get entities(): T[] { - return Object.values(this.entities$.getValue()); + get allEntities(): T[] { + return Object.values(this.allEntities$.getValue()); } get filteredEntities(): T[] { return Object.values(this.filteredEntities$.getValue()); } - get selectedEntitiesIds(): string[] { - return Object.values(this.selectedEntitiesIds$.getValue()); + get selectedEntities(): T[] { + return Object.values(this.selectedEntities$.getValue()); } get displayedEntities(): T[] { @@ -28,85 +26,62 @@ export class ScreenStateService { } map(func: (state: T[]) => K): Observable { - return this.entities$.asObservable().pipe( + return this.allEntities$.asObservable().pipe( map((state: T[]) => func(state)), distinctUntilChanged() ); } setEntities(newEntities: Partial): void { - this.entities$.next(newEntities); + this.allEntities$.next(newEntities); } setFilteredEntities(newEntities: Partial): void { this.filteredEntities$.next(newEntities); } - setSelectedEntitiesIds(newEntities: Partial): void { - this.selectedEntitiesIds$.next(newEntities); + setSelectedEntities(newEntities: Partial): void { + this.selectedEntities$.next(newEntities); } setDisplayedEntities(newEntities: Partial): void { this.displayedEntities$.next(newEntities); } - setIdKey(value: string): void { - this._idKey = value; - } - get areAllEntitiesSelected(): boolean { - return ( - this.displayedEntities.length !== 0 && - this.selectedEntitiesIds.length === this.displayedEntities.length - ); + return this.displayedEntities.length !== 0 && this.selectedEntities.length === this.displayedEntities.length; } get areSomeEntitiesSelected$(): Observable { - return this.selectedEntitiesIds$.pipe(map(all => all.length > 0)); + return this.selectedEntities$.pipe(map(all => all.length > 0)); } isSelected(entity: T): boolean { - return this.selectedEntitiesIds.indexOf(entity[this._getIdKey]) !== -1; + return this.selectedEntities.indexOf(entity) !== -1; } toggleEntitySelected(entity: T): void { - const currentEntityIdx = this.selectedEntitiesIds.indexOf(entity[this._getIdKey]); + const currentEntityIdx = this.selectedEntities.indexOf(entity); if (currentEntityIdx === -1) { - const currentEntityId = entity[this._getIdKey]; - return this.setSelectedEntitiesIds([...this.selectedEntitiesIds, currentEntityId]); + return this.setSelectedEntities([...this.selectedEntities, entity]); } - - this.setSelectedEntitiesIds( - this.selectedEntitiesIds.filter((el, idx) => idx !== currentEntityIdx) - ); + this.setSelectedEntities(this.selectedEntities.filter((el, idx) => idx !== currentEntityIdx)); } toggleSelectAll(): void { - if (this.areAllEntitiesSelected) return this.setSelectedEntitiesIds([]); - this.setSelectedEntitiesIds(this._displayedEntitiesIds); + if (this.areAllEntitiesSelected) return this.setSelectedEntities([]); + this.setSelectedEntities(this.displayedEntities); } updateSelection(): void { - if (!this._idKey) return; - - const ids = this._displayedEntitiesIds.filter(id => this.selectedEntitiesIds.includes(id)); - this.setSelectedEntitiesIds(ids); + const items = this.displayedEntities.filter(item => this.selectedEntities.includes(item)); + this.setSelectedEntities(items); } logCurrentState(): void { - console.log('Entities', this.entities); + console.log('Entities', this.allEntities); console.log('Displayed', this.displayedEntities); console.log('Filtered', this.filteredEntities); - console.log('Selected', this.selectedEntitiesIds); - } - - private get _displayedEntitiesIds(): string[] { - return this.displayedEntities.map(entity => entity[this._getIdKey]); - } - - private get _getIdKey(): string { - if (!this._idKey) throw new Error('Not implemented'); - - return this._idKey; + console.log('Selected', this.selectedEntities); } } 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 49279535a..25a8f19b7 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 @@ -12,10 +12,7 @@ export class SearchService { query: [''] }); - constructor( - private readonly _screenStateService: ScreenStateService, - private readonly _formBuilder: FormBuilder - ) { + constructor(private readonly _screenStateService: ScreenStateService, private readonly _formBuilder: FormBuilder) { this.searchForm.valueChanges.subscribe(() => this.executeSearch()); } @@ -26,17 +23,14 @@ export class SearchService { } executeSearchImmediately(): void { - const displayed = - this._screenStateService.filteredEntities || this._screenStateService.entities; + const displayed = this._screenStateService.filteredEntities || this._screenStateService.allEntities; if (!this._searchKey) { return this._screenStateService.setDisplayedEntities(displayed); } this._screenStateService.setDisplayedEntities( - displayed.filter(entity => - this._searchField(entity).toLowerCase().includes(this._searchValue) - ) + displayed.filter(entity => this._searchField(entity).toLowerCase().includes(this._searchValue)) ); this._screenStateService.updateSelection(); } diff --git a/apps/red-ui/src/app/services/user-preference.service.ts b/apps/red-ui/src/app/services/user-preference.service.ts index b77e13d58..e2999f0ce 100644 --- a/apps/red-ui/src/app/services/user-preference.service.ts +++ b/apps/red-ui/src/app/services/user-preference.service.ts @@ -1,15 +1,36 @@ import { Injectable } from '@angular/core'; +import { UserPreferenceControllerService } from '@redaction/red-ui-http'; + +interface UserAttributes { + [p: string]: string[]; +} @Injectable({ providedIn: 'root' }) export class UserPreferenceService { + private _userAttributes: UserAttributes = {}; + + constructor(private readonly _userPreferenceControllerService: UserPreferenceControllerService) { + _userPreferenceControllerService.getAllUserAttributes().subscribe(attributes => { + this._userAttributes = attributes ?? {}; + }); + } + + get userAttributes(): UserAttributes { + return this._userAttributes; + } + get areDevFeaturesEnabled() { const value = sessionStorage.getItem('redaction.enable-dev-features'); - if (value) { - return value === 'true'; + return value ? value === 'true' : false; + } + + getLastOpenedFileId(key: string): string { + if (this.userAttributes[key]?.length > 0) { + return this.userAttributes[key][0]; } - return false; + return ''; } toggleDevFeatures() { diff --git a/apps/red-ui/src/app/state/app-state.service.ts b/apps/red-ui/src/app/state/app-state.service.ts index f8117c4ea..8acaa3a7b 100644 --- a/apps/red-ui/src/app/state/app-state.service.ts +++ b/apps/red-ui/src/app/state/app-state.service.ts @@ -2,7 +2,6 @@ import { EventEmitter, Injectable } from '@angular/core'; import { DictionaryControllerService, Dossier, - DossierControllerService, DossierTemplateControllerService, FileAttributesConfig, FileAttributesControllerService, @@ -21,6 +20,7 @@ import { FileStatusWrapper } from '@models/file/file-status.wrapper'; import { DossierWrapper } from './model/dossier.wrapper'; import { TypeValueWrapper } from '@models/file/type-value.wrapper'; import { DossierTemplateModelWrapper } from '@models/file/dossier-template-model.wrapper'; +import { DossiersService } from '../modules/dossier/services/dossiers.service'; export interface AppState { dossiers: DossierWrapper[]; @@ -46,7 +46,7 @@ export class AppStateService { constructor( private readonly _router: Router, private readonly _userService: UserService, - private readonly _dossierControllerService: DossierControllerService, + private readonly _dossiersService: DossiersService, private readonly _notificationService: NotificationService, private readonly _reanalysisControllerService: ReanalysisControllerService, private readonly _translateService: TranslateService, @@ -65,15 +65,15 @@ export class AppStateService { activeDictionaryType: null }; - this._router.events.subscribe((event: Event) => { + _router.events.subscribe((event: Event) => { if (AppStateService._isFileOverviewRoute(event)) { const url = (event as ResolveStart).url.replace('/main/dossiers/', ''); const [dossierId, , fileId] = url.split(/[/?]/); - this.activateFile(dossierId, fileId); + return this.activateFile(dossierId, fileId); } if (AppStateService._isDossierOverviewRoute(event)) { const dossierId = (event as ResolveStart).url.replace('/main/dossiers/', ''); - this.activateDossier(dossierId); + return this.activateDossier(dossierId); } if (AppStateService._isRandomRoute(event)) { this._appState.activeDossierId = null; @@ -88,11 +88,7 @@ export class AppStateService { } get aggregatedFiles(): FileStatusWrapper[] { - const result: FileStatusWrapper[] = []; - this._appState.dossiers.forEach(p => { - result.push(...p.files); - }); - return result; + return this.allDossiers.reduce((acc, { files }) => [...acc, ...files], []); } get activeDossierTemplateId(): string { @@ -128,7 +124,7 @@ export class AppStateService { } get activeDossier(): DossierWrapper | undefined { - return this._appState.dossiers.find(p => p.dossierId === this.activeDossierId); + return this.allDossiers.find(p => p.dossierId === this.activeDossierId); } get allDossiers(): DossierWrapper[] { @@ -147,18 +143,14 @@ export class AppStateService { return this._appState.activeFileId; } - get totalAnalysedPages() { + get totalAnalysedPages(): number { return this._appState.totalAnalysedPages; } - get totalPeople() { + get totalPeople(): number { return this._appState.totalPeople; } - get totalDocuments() { - return this._appState.totalDocuments; - } - private static _isFileOverviewRoute(event: Event) { return event instanceof ResolveStart && event.url.includes('/main/dossiers/') && event.url.includes('/file/'); } @@ -177,18 +169,15 @@ export class AppStateService { } } - getDictionaryColor(type?: string, dossierTemplateId?: string) { - if (!dossierTemplateId && this.activeDossier) { - dossierTemplateId = this.activeDossier.dossierTemplateId; - } + getDictionaryColor(type?: string, dossierTemplateId = this.activeDossier?.dossierTemplateId) { if (!dossierTemplateId) { - dossierTemplateId = this.dossierTemplates.length > 0 ? this.dossierTemplates[0].dossierTemplateId : undefined; + dossierTemplateId = this.dossierTemplates[0]?.dossierTemplateId; } if (!dossierTemplateId) { return undefined; } const color = this._dictionaryData[dossierTemplateId][type]?.hexColor; - return color ? color : this._dictionaryData[dossierTemplateId]['default'].hexColor; + return color ?? this._dictionaryData[dossierTemplateId]['default'].hexColor; } getDossierTemplateById(id: string): DossierTemplateModelWrapper { @@ -220,20 +209,21 @@ export class AppStateService { } async loadAllDossiers(emitEvents: boolean = true) { - const dossiers = await this._dossierControllerService.getDossiers().toPromise(); - if (dossiers) { - const mappedDossiers = dossiers.map(p => new DossierWrapper(p, this._getExistingFiles(p.dossierId))); - - const fileData = await this._statusControllerService.getFileStatusForDossiers(mappedDossiers.map(p => p.dossierId)).toPromise(); - - for (const dossierId of Object.keys(fileData)) { - const dossier = mappedDossiers.find(p => p.dossierId === dossierId); - this._processFiles(dossier, fileData[dossierId], emitEvents); - } - - this._appState.dossiers = mappedDossiers; - this._computeStats(); + const dossiers = await this._dossiersService.getAll(); + if (!dossiers) { + return; } + + const mappedDossiers = dossiers.map(p => new DossierWrapper(p, this._getExistingFiles(p.dossierId))); + const fileData = await this._statusControllerService.getFileStatusForDossiers(mappedDossiers.map(p => p.dossierId)).toPromise(); + + for (const dossierId of Object.keys(fileData)) { + const dossier = mappedDossiers.find(p => p.dossierId === dossierId); + this._processFiles(dossier, fileData[dossierId], emitEvents); + } + + this._appState.dossiers = mappedDossiers; + this._computeStats(); } async reloadActiveFile() { @@ -246,8 +236,8 @@ export class AppStateService { const activeFileWrapper = new FileStatusWrapper( activeFile, this._userService.getNameForId(activeFile.currentReviewer), - this.activeDossier.dossierTemplateId, - this._appState.fileAttributesConfig[this.activeDossier.dossierTemplateId] + this.activeDossierTemplateId, + this._appState.fileAttributesConfig[this.activeDossierTemplateId] ); this.activeDossier.files = this.activeDossier.files.map(file => file.fileId === activeFileWrapper.fileId ? activeFileWrapper : file @@ -261,32 +251,26 @@ export class AppStateService { return activeFileWrapper; } - async getFiles(dossier?: DossierWrapper, emitEvents: boolean = true) { - if (!dossier) { - dossier = this.activeDossier; - } + async getFiles(dossier: DossierWrapper = this.activeDossier, emitEvents = true) { const files = await this._statusControllerService.getDossierStatus(dossier.dossierId).toPromise(); return this._processFiles(dossier, files, emitEvents); } - async reanalyzeDossier(dossier?: DossierWrapper) { - if (!dossier) { - dossier = this.activeDossier; - } - await this._reanalysisControllerService.reanalyzeDossier(dossier.dossierId).toPromise(); + async reanalyzeDossier({ dossierId }: DossierWrapper = this.activeDossier) { + await this._reanalysisControllerService.reanalyzeDossier(dossierId).toPromise(); } - activateDossier(dossierId: string) { + activateDossier(dossierId: string): void { this._appState.activeFileId = null; this._appState.activeDossierId = dossierId; if (!this.activeDossier) { this._appState.activeDossierId = null; - this._router.navigate(['/main/dossiers']); + this._router.navigate(['/main/dossiers']).then(); return; - } else { - this.updateDossierDictionary(this.activeDossier.dossierTemplateId, dossierId); } + + this.updateDossierDictionary(this.activeDossier.dossierTemplateId, dossierId); } updateDossierDictionary(dossierTemplateId: string, dossierId: string) { @@ -302,7 +286,7 @@ export class AppStateService { } activateFile(dossierId: string, fileId: string) { - if (this._appState.activeDossierId === dossierId && this._appState.activeFileId === fileId) return; + if (this.activeDossierId === dossierId && this.activeFileId === fileId) return; this.activateDossier(dossierId); if (this.activeDossier) { this._appState.activeFileId = fileId; @@ -341,29 +325,25 @@ export class AppStateService { } deleteDossier(dossier: DossierWrapper) { - return this._dossierControllerService - .deleteDossier(dossier.dossierId) - .toPromise() - .then( - () => { - const index = this._appState.dossiers.findIndex(p => p.dossier.dossierId === dossier.dossierId); - this._appState.dossiers.splice(index, 1); - this._appState.dossiers = [...this._appState.dossiers]; - }, - () => { - this._notificationService.showToastNotification( - this._translateService.instant('dossiers.delete.delete-failed', dossier), - null, - NotificationType.ERROR - ); - } - ); + return this._dossiersService.delete(dossier.dossierId).then( + () => { + const index = this.allDossiers.findIndex(p => p.dossierId === dossier.dossierId); + this._appState.dossiers.splice(index, 1); + }, + () => { + this._notificationService.showToastNotification( + this._translateService.instant('dossiers.delete.delete-failed', dossier), + null, + NotificationType.ERROR + ); + } + ); } - async addOrUpdateDossier(dossier: Dossier) { + async createOrUpdateDossier(dossier: Dossier) { try { - const updatedDossier = await this._dossierControllerService.createOrUpdateDossier(dossier).toPromise(); - let foundDossier = this._appState.dossiers.find(p => p.dossier.dossierId === updatedDossier.dossierId); + const updatedDossier = await this._dossiersService.createOrUpdate(dossier); + let foundDossier = this.allDossiers.find(p => p.dossierId === updatedDossier.dossierId); if (foundDossier) { Object.assign((foundDossier.dossier = updatedDossier)); } else { @@ -385,7 +365,7 @@ export class AppStateService { async reloadActiveDossierFiles() { if (this.activeDossierId) { - await this.getFiles(this.activeDossier); + await this.getFiles(); } } @@ -408,7 +388,7 @@ export class AppStateService { } async loadAllDossiersIfNecessary() { - if (!this._appState.dossiers.length) { + if (!this.allDossiers.length) { await this.loadAllDossiers(); } } @@ -642,7 +622,7 @@ export class AppStateService { return [typeObs, colorsObs]; } - async loadDictionaryData() { + async loadDictionaryData(): Promise { const obj = {}; const observables = []; @@ -660,9 +640,9 @@ export class AppStateService { this._dictionaryData = obj; } - private _getExistingFiles(dossierId: string) { - const found = this._appState.dossiers.find(p => p.dossier.dossierId === dossierId); - return found ? found.files : []; + private _getExistingFiles(dossierId: string): FileStatusWrapper[] { + const dossier = this.allDossiers.find(p => p.dossierId === dossierId); + return dossier?.files ?? []; } private _processFiles(dossier: DossierWrapper, files: FileStatus[], emitEvents: boolean = true) { @@ -727,25 +707,18 @@ export class AppStateService { let totalAnalysedPages = 0; let totalDocuments = 0; const totalPeople = new Set(); - this._appState.dossiers.forEach(p => { - totalDocuments += p.files.length; - if (p.dossier.memberIds) { - p.dossier.memberIds.forEach(m => totalPeople.add(m)); + this.allDossiers.forEach(d => { + totalDocuments += d.files.length; + if (d.dossier.memberIds) { + d.dossier.memberIds.forEach(m => totalPeople.add(m)); } - let numberOfPages = 0; - p.files.forEach(f => { - numberOfPages += f.numberOfPages; - }); - p.totalNumberOfPages = numberOfPages; - totalAnalysedPages += numberOfPages; + + d.totalNumberOfPages = d.files.reduce((acc, file) => acc + file.numberOfPages, 0); + totalAnalysedPages += d.totalNumberOfPages; }); this._appState.totalPeople = totalPeople.size; this._appState.totalAnalysedPages = totalAnalysedPages; this._appState.totalDocuments = totalDocuments; - - if (this.activeDossierId && this.activeFileId) { - this.activateFile(this.activeDossierId, this.activeFileId); - } } } diff --git a/apps/red-ui/src/app/utils/sorters/status-sorter.ts b/apps/red-ui/src/app/utils/sorters/status-sorter.ts index d4d2ed8d8..94ec85387 100644 --- a/apps/red-ui/src/app/utils/sorters/status-sorter.ts +++ b/apps/red-ui/src/app/utils/sorters/status-sorter.ts @@ -9,5 +9,5 @@ export const StatusSorter = { UNDER_REVIEW: 15, UNDER_APPROVAL: 20, APPROVED: 25, - byKey: (a: { key: string }, b: { key: string }) => StatusSorter[a.key] - StatusSorter[b.key] + byKey: (a: { key?: string }, b: { key?: string }) => StatusSorter[a.key] - StatusSorter[b.key] }; From 75553f562acf9b69a9dcf5f68b7b97b6353171b4 Mon Sep 17 00:00:00 2001 From: Dan Percic Date: Thu, 15 Jul 2021 01:09:28 +0300 Subject: [PATCH 2/6] refactor notification service --- .../app/components/toast/toast.component.html | 18 +--- .../app/components/toast/toast.component.ts | 13 +-- .../add-edit-dictionary-dialog.component.ts | 14 ++-- ...edit-dossier-attribute-dialog.component.ts | 28 +++---- .../edit-color-dialog.component.ts | 17 ++-- ...-attributes-csv-import-dialog.component.ts | 18 +--- .../digital-signature-screen.component.html | 49 +++-------- .../digital-signature-screen.component.ts | 52 ++++-------- .../screens/rules/rules-screen.component.ts | 55 ++++++------- .../smtp-config-screen.component.ts | 28 +++---- .../watermark/watermark-screen.component.ts | 66 +++++++++------ .../dossier-details.component.ts | 10 +-- .../page-exclusion.component.ts | 17 ++-- .../team-members-manager.component.ts | 14 +--- ...sign-reviewer-approver-dialog.component.ts | 24 ++---- .../edit-dossier-dialog.component.ts | 25 ++---- .../edit-dossier-general-info.component.ts | 12 +-- .../force-redaction-dialog.component.ts | 4 +- .../manual-annotation-dialog.component.ts | 4 +- .../dossier-overview-screen.component.ts | 18 +--- .../file-preview-screen.component.ts | 31 +++---- .../services/manual-annotation.service.ts | 19 ++--- .../shared/base/auto-unsubscribe.component.ts | 5 +- .../shared/base/base-listing.component.ts | 1 - .../file-download-btn.component.ts | 27 +++--- .../services/dictionary-save.service.ts | 31 ++----- .../src/app/services/error-message.service.ts | 6 +- .../src/app/services/notification.service.ts | 47 ----------- .../src/app/services/toaster.service.ts | 82 +++++++++++++++++++ .../red-ui/src/app/state/app-state.service.ts | 22 ++--- 30 files changed, 303 insertions(+), 454 deletions(-) delete mode 100644 apps/red-ui/src/app/services/notification.service.ts create mode 100644 apps/red-ui/src/app/services/toaster.service.ts diff --git a/apps/red-ui/src/app/components/toast/toast.component.html b/apps/red-ui/src/app/components/toast/toast.component.html index 45f3d662b..cfffe69b4 100644 --- a/apps/red-ui/src/app/components/toast/toast.component.html +++ b/apps/red-ui/src/app/components/toast/toast.component.html @@ -2,24 +2,12 @@
{{ title }}
-
-
+
+
{{ message }}
-
+
{{ action.title }} diff --git a/apps/red-ui/src/app/components/toast/toast.component.ts b/apps/red-ui/src/app/components/toast/toast.component.ts index 9b4e233d8..e61e2b967 100644 --- a/apps/red-ui/src/app/components/toast/toast.component.ts +++ b/apps/red-ui/src/app/components/toast/toast.component.ts @@ -1,26 +1,21 @@ import { Component } from '@angular/core'; import { Toast, ToastPackage, ToastrService } from 'ngx-toastr'; +import { ToasterOptions } from '@services/toaster.service'; @Component({ - selector: 'redaction-toast', templateUrl: './toast.component.html', styleUrls: ['./toast.component.scss'] }) export class ToastComponent extends Toast { - constructor( - protected readonly _toastrService: ToastrService, - readonly toastPackage: ToastPackage - ) { + constructor(protected readonly _toastrService: ToastrService, readonly toastPackage: ToastPackage) { super(_toastrService, toastPackage); } get actions() { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - return this.options.actions; + return (this.options as ToasterOptions)?.actions; } - callAction($event: MouseEvent, action: Function) { + callAction($event: MouseEvent, action: () => void) { $event.stopPropagation(); if (action) { action(); diff --git a/apps/red-ui/src/app/modules/admin/dialogs/add-edit-dictionary-dialog/add-edit-dictionary-dialog.component.ts b/apps/red-ui/src/app/modules/admin/dialogs/add-edit-dictionary-dialog/add-edit-dictionary-dialog.component.ts index f77311212..eb80f32d9 100644 --- a/apps/red-ui/src/app/modules/admin/dialogs/add-edit-dictionary-dialog/add-edit-dictionary-dialog.component.ts +++ b/apps/red-ui/src/app/modules/admin/dialogs/add-edit-dictionary-dialog/add-edit-dictionary-dialog.component.ts @@ -3,7 +3,7 @@ import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { DictionaryControllerService, TypeValue } from '@redaction/red-ui-http'; import { Observable } from 'rxjs'; -import { NotificationService, NotificationType } from '@services/notification.service'; +import { Toaster } from '@services/toaster.service'; import { TranslateService } from '@ngx-translate/core'; import { TypeValueWrapper } from '@models/file/type-value.wrapper'; import { humanize } from '../../../../utils/functions'; @@ -21,7 +21,7 @@ export class AddEditDictionaryDialogComponent { constructor( private readonly _dictionaryControllerService: DictionaryControllerService, private readonly _formBuilder: FormBuilder, - private readonly _notificationService: NotificationService, + private readonly _toaster: Toaster, private readonly _translateService: TranslateService, private readonly _dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) @@ -89,20 +89,16 @@ export class AddEditDictionaryDialogComponent { () => this._dialogRef.close(true), error => { if (error.status === 409) { - this._notifyError('add-edit-dictionary.error.dictionary-already-exists'); + this._toaster.error('add-edit-dictionary.error.dictionary-already-exists'); } else if (error.status === 400) { - this._notifyError('add-edit-dictionary.error.invalid-color-or-rank'); + this._toaster.error('add-edit-dictionary.error.invalid-color-or-rank'); } else { - this._notifyError('add-edit-dictionary.error.generic'); + this._toaster.error('add-edit-dictionary.error.generic'); } } ); } - private _notifyError(message: string) { - this._notificationService.showToastNotification(this._translateService.instant(message), null, NotificationType.ERROR); - } - private _formToObject(): TypeValue { return { caseInsensitive: !this.dictionaryForm.get('caseSensitive').value, diff --git a/apps/red-ui/src/app/modules/admin/dialogs/add-edit-dossier-attribute-dialog/add-edit-dossier-attribute-dialog.component.ts b/apps/red-ui/src/app/modules/admin/dialogs/add-edit-dossier-attribute-dialog/add-edit-dossier-attribute-dialog.component.ts index 498af6fba..62094361f 100644 --- a/apps/red-ui/src/app/modules/admin/dialogs/add-edit-dossier-attribute-dialog/add-edit-dossier-attribute-dialog.component.ts +++ b/apps/red-ui/src/app/modules/admin/dialogs/add-edit-dossier-attribute-dialog/add-edit-dossier-attribute-dialog.component.ts @@ -1,12 +1,12 @@ -import { Component, Inject } from '@angular/core'; +import { Component, Inject, OnDestroy } from '@angular/core'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { DossierAttributeConfig, FileAttributeConfig } from '@redaction/red-ui-http'; import { AppStateService } from '@state/app-state.service'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; -import { LoadingService } from '@services/loading.service'; -import { ErrorMessageService } from '@services/error-message.service'; -import { NotificationService, NotificationType } from '@services/notification.service'; +import { LoadingService } from '../../../../services/loading.service'; import { HttpErrorResponse } from '@angular/common/http'; +import { Toaster } from '../../../../services/toaster.service'; +import { AutoUnsubscribeComponent } from '../../../shared/base/auto-unsubscribe.component'; import { DossierAttributesService } from '@shared/services/controller-wrappers/dossier-attributes.service'; @Component({ @@ -14,7 +14,7 @@ import { DossierAttributesService } from '@shared/services/controller-wrappers/d templateUrl: './add-edit-dossier-attribute-dialog.component.html', styleUrls: ['./add-edit-dossier-attribute-dialog.component.scss'] }) -export class AddEditDossierAttributeDialogComponent { +export class AddEditDossierAttributeDialogComponent extends AutoUnsubscribeComponent implements OnDestroy { dossierAttributeForm: FormGroup; dossierAttribute: DossierAttributeConfig; dossierTemplateId: string; @@ -30,12 +30,12 @@ export class AddEditDossierAttributeDialogComponent { private readonly _formBuilder: FormBuilder, private readonly _loadingService: LoadingService, private readonly _dossierAttributesService: DossierAttributesService, - private readonly _errorMessageService: ErrorMessageService, - private readonly _notificationService: NotificationService, + private readonly _toaster: Toaster, public dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data: { dossierAttribute: DossierAttributeConfig; dossierTemplateId: string } ) { + super(); this.dossierAttribute = data.dossierAttribute; this.dossierTemplateId = data.dossierTemplateId; @@ -76,14 +76,10 @@ export class AddEditDossierAttributeDialogComponent { () => { this.dialogRef.close(true); }, - (err: HttpErrorResponse) => { - this._loadingService.stop(); - this._notificationService.showToastNotification( - this._errorMessageService.getMessage(err, 'add-edit-dossier-attribute.error.generic'), - null, - NotificationType.ERROR - ); - } - ); + (error: HttpErrorResponse) => { + this._loadingService.stop(); + this._toaster.error('add-edit-dossier-attribute.error.generic', { error: error }); + } + ); } } diff --git a/apps/red-ui/src/app/modules/admin/dialogs/edit-color-dialog/edit-color-dialog.component.ts b/apps/red-ui/src/app/modules/admin/dialogs/edit-color-dialog/edit-color-dialog.component.ts index 37ce4623e..7eacbff89 100644 --- a/apps/red-ui/src/app/modules/admin/dialogs/edit-color-dialog/edit-color-dialog.component.ts +++ b/apps/red-ui/src/app/modules/admin/dialogs/edit-color-dialog/edit-color-dialog.component.ts @@ -1,7 +1,7 @@ import { Component, Inject } from '@angular/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { Colors, DictionaryControllerService } from '@redaction/red-ui-http'; -import { NotificationService, NotificationType } from '@services/notification.service'; +import { Toaster } from '../../../../services/toaster.service'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { TranslateService } from '@ngx-translate/core'; @@ -20,7 +20,7 @@ export class EditColorDialogComponent { constructor( private readonly _formBuilder: FormBuilder, private readonly _dictionaryControllerService: DictionaryControllerService, - private readonly _notificationService: NotificationService, + private readonly _toaster: Toaster, private readonly _translateService: TranslateService, private readonly _dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) @@ -49,17 +49,10 @@ export class EditColorDialogComponent { try { await this._dictionaryControllerService.setColors(colors, this._dossierTemplateId).toPromise(); this._dialogRef.close(true); - this._notificationService.showToastNotification( - this._translateService.instant('edit-color-dialog.success', { - color: this._translateService.instant('default-colors-screen.types.' + this.colorKey) - }) - ); + const color = this._translateService.instant(`default-colors-screen.types.${this.colorKey}`); + this._toaster.info('edit-color-dialog.success', { params: { color: color } }); } catch (e) { - this._notificationService.showToastNotification( - this._translateService.instant('edit-color-dialog.error'), - null, - NotificationType.ERROR - ); + this._toaster.error('edit-color-dialog.error'); } } } 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 b09b644ae..d933b71e8 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 @@ -6,7 +6,7 @@ import * as Papa from 'papaparse'; import { FileAttributeConfig, FileAttributesConfig, FileAttributesControllerService } from '@redaction/red-ui-http'; import { Observable } from 'rxjs'; import { map, startWith } from 'rxjs/operators'; -import { NotificationService, NotificationType } from '@services/notification.service'; +import { Toaster } from '../../../../services/toaster.service'; import { TranslateService } from '@ngx-translate/core'; import { FilterService } from '@shared/services/filter.service'; import { SearchService } from '@shared/services/search.service'; @@ -48,7 +48,7 @@ export class FileAttributesCsvImportDialogComponent extends BaseListingComponent private readonly _appStateService: AppStateService, private readonly _fileAttributesControllerService: FileAttributesControllerService, private readonly _translateService: TranslateService, - private readonly _notificationService: NotificationService, + private readonly _toaster: Toaster, private readonly _formBuilder: FormBuilder, public dialogRef: MatDialogRef, protected readonly _injector: Injector, @@ -202,19 +202,9 @@ export class FileAttributesCsvImportDialogComponent extends BaseListingComponent try { await this._fileAttributesControllerService.setFileAttributesConfig(fileAttributes, this.dossierTemplateId).toPromise(); - this._notificationService.showToastNotification( - this._translateService.instant('file-attributes-csv-import.save.success', { - count: this.activeFields.length - }), - null, - NotificationType.SUCCESS - ); + this._toaster.success('file-attributes-csv-import.save.success', { params: { count: this.activeFields.length } }); } catch (e) { - this._notificationService.showToastNotification( - this._translateService.instant('file-attributes-csv-import.save.error'), - null, - NotificationType.ERROR - ); + this._toaster.error('file-attributes-csv-import.save.error'); } this.dialogRef.close(true); diff --git a/apps/red-ui/src/app/modules/admin/screens/digital-signature/digital-signature-screen.component.html b/apps/red-ui/src/app/modules/admin/screens/digital-signature/digital-signature-screen.component.html index 18b923648..36b224fac 100644 --- a/apps/red-ui/src/app/modules/admin/screens/digital-signature/digital-signature-screen.component.html +++ b/apps/red-ui/src/app/modules/admin/screens/digital-signature/digital-signature-screen.component.html @@ -22,19 +22,8 @@
- - + + -
- +
+ @@ -67,9 +48,7 @@ > @@ -78,9 +57,7 @@
@@ -89,9 +66,7 @@
@@ -100,9 +75,7 @@
@@ -139,6 +112,4 @@
- + diff --git a/apps/red-ui/src/app/modules/admin/screens/digital-signature/digital-signature-screen.component.ts b/apps/red-ui/src/app/modules/admin/screens/digital-signature/digital-signature-screen.component.ts index 0ebbfb000..a5ac0a556 100644 --- a/apps/red-ui/src/app/modules/admin/screens/digital-signature/digital-signature-screen.component.ts +++ b/apps/red-ui/src/app/modules/admin/screens/digital-signature/digital-signature-screen.component.ts @@ -1,17 +1,17 @@ -import { Component } from '@angular/core'; +import { Component, OnDestroy } from '@angular/core'; import { DigitalSignature, DigitalSignatureControllerService } from '@redaction/red-ui-http'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; -import { NotificationService, NotificationType } from '@services/notification.service'; -import { TranslateService } from '@ngx-translate/core'; +import { Toaster } from '../../../../services/toaster.service'; import { PermissionsService } from '@services/permissions.service'; import { lastIndexOfEnd } from '@utils/functions'; +import { AutoUnsubscribeComponent } from '../../../shared/base/auto-unsubscribe.component'; @Component({ selector: 'redaction-digital-signature-screen', templateUrl: './digital-signature-screen.component.html', styleUrls: ['./digital-signature-screen.component.scss'] }) -export class DigitalSignatureScreenComponent { +export class DigitalSignatureScreenComponent extends AutoUnsubscribeComponent implements OnDestroy { digitalSignature: DigitalSignature; digitalSignatureForm: FormGroup; @@ -20,11 +20,11 @@ export class DigitalSignatureScreenComponent { constructor( private readonly _digitalSignatureControllerService: DigitalSignatureControllerService, - private readonly _notificationService: NotificationService, + private readonly _toaster: Toaster, private readonly _formBuilder: FormBuilder, - private readonly _translateService: TranslateService, readonly permissionsService: PermissionsService ) { + super(); this.loadDigitalSignatureAndInitializeForm(); } @@ -43,50 +43,28 @@ export class DigitalSignatureScreenComponent { ? this._digitalSignatureControllerService.updateDigitalSignature(digitalSignature) : this._digitalSignatureControllerService.saveDigitalSignature(digitalSignature); - observable.subscribe( + this.addSubscription = observable.subscribe( () => { this.loadDigitalSignatureAndInitializeForm(); - this._notificationService.showToastNotification( - this._translateService.instant('digital-signature-screen.action.save-success'), - null, - NotificationType.SUCCESS - ); + this._toaster.success('digital-signature-screen.action.save-success'); }, error => { if (error.status === 400) { - this._notificationService.showToastNotification( - this._translateService.instant('digital-signature-screen.action.certificate-not-valid-error'), - null, - NotificationType.ERROR - ); + this._toaster.error('digital-signature-screen.action.certificate-not-valid-error'); } else { - this._notificationService.showToastNotification( - this._translateService.instant('digital-signature-screen.action.save-error'), - null, - NotificationType.ERROR - ); + this._toaster.error('digital-signature-screen.action.save-error'); } } ); } removeDigitalSignature() { - this._digitalSignatureControllerService.deleteDigitalSignature().subscribe( + this.addSubscription = this._digitalSignatureControllerService.deleteDigitalSignature().subscribe( () => { this.loadDigitalSignatureAndInitializeForm(); - this._notificationService.showToastNotification( - this._translateService.instant('digital-signature-screen.action.delete-success'), - null, - NotificationType.SUCCESS - ); + this._toaster.success('digital-signature-screen.action.delete-success'); }, - () => { - this._notificationService.showToastNotification( - this._translateService.instant('digital-signature-screen.action.delete-error'), - null, - NotificationType.ERROR - ); - } + () => this._toaster.error('digital-signature-screen.action.delete-error') ); } @@ -105,7 +83,7 @@ export class DigitalSignatureScreenComponent { loadDigitalSignatureAndInitializeForm() { this.viewReady = false; - this._digitalSignatureControllerService + this.addSubscription = this._digitalSignatureControllerService .getDigitalSignature() .subscribe( digitalSignature => { @@ -123,8 +101,6 @@ export class DigitalSignatureScreenComponent { }); } - formChanged() {} - private _initForm() { this.digitalSignatureForm = this._formBuilder.group({ certificateName: [this.digitalSignature.certificateName, Validators.required], diff --git a/apps/red-ui/src/app/modules/admin/screens/rules/rules-screen.component.ts b/apps/red-ui/src/app/modules/admin/screens/rules/rules-screen.component.ts index 8f266eebd..d8a6552a7 100644 --- a/apps/red-ui/src/app/modules/admin/screens/rules/rules-screen.component.ts +++ b/apps/red-ui/src/app/modules/admin/screens/rules/rules-screen.component.ts @@ -1,7 +1,7 @@ -import { Component, ElementRef, ViewChild } from '@angular/core'; +import { Component, ElementRef, OnInit, ViewChild } from '@angular/core'; import { PermissionsService } from '@services/permissions.service'; import { RulesControllerService } from '@redaction/red-ui-http'; -import { NotificationService, NotificationType } from '@services/notification.service'; +import { Toaster } from '../../../../services/toaster.service'; import { TranslateService } from '@ngx-translate/core'; import { saveAs } from 'file-saver'; import { ComponentHasChanges } from '@guards/can-deactivate.guard'; @@ -17,7 +17,7 @@ import IStandaloneEditorConstructionOptions = monaco.editor.IStandaloneEditorCon templateUrl: './rules-screen.component.html', styleUrls: ['./rules-screen.component.scss'] }) -export class RulesScreenComponent extends ComponentHasChanges { +export class RulesScreenComponent extends ComponentHasChanges implements OnInit { editorOptions: IStandaloneEditorConstructionOptions = { theme: 'vs', language: 'java', @@ -39,13 +39,16 @@ export class RulesScreenComponent extends ComponentHasChanges { readonly permissionsService: PermissionsService, private readonly _rulesControllerService: RulesControllerService, private readonly _appStateService: AppStateService, - private readonly _notificationService: NotificationService, + private readonly _toaster: Toaster, protected readonly _translateService: TranslateService, private readonly _activatedRoute: ActivatedRoute ) { super(_translateService); - this._appStateService.activateDossierTemplate(_activatedRoute.snapshot.params.dossierTemplateId); - this._initialize(); + _appStateService.activateDossierTemplate(_activatedRoute.snapshot.params.dossierTemplateId); + } + + async ngOnInit() { + await this._initialize(); } get hasChanges(): boolean { @@ -83,27 +86,20 @@ export class RulesScreenComponent extends ComponentHasChanges { async save(): Promise { this.processing = true; - this._rulesControllerService + await this._rulesControllerService .uploadRules({ rules: this._codeEditor.getModel().getValue(), dossierTemplateId: this._appStateService.activeDossierTemplateId }) - .subscribe( - () => { - this._initialize(); - this._notificationService.showToastNotification( - this._translateService.instant('rules-screen.success.generic'), - null, - NotificationType.SUCCESS - ); + .toPromise() + .then( + async () => { + await this._initialize(); + this._toaster.success('rules-screen.success.generic'); }, () => { this.processing = false; - this._notificationService.showToastNotification( - this._translateService.instant('rules-screen.error.generic'), - null, - NotificationType.ERROR - ); + this._toaster.error('rules-screen.error.generic'); } ); } @@ -148,13 +144,16 @@ export class RulesScreenComponent extends ComponentHasChanges { } as IModelDeltaDecoration; } - private _initialize() { - this._rulesControllerService.downloadRules(this._appStateService.activeDossierTemplateId).subscribe( - rules => { - this.currentLines = this.initialLines = rules.rules.split('\n'); - this.revert(); - }, - () => (this.processing = false) - ); + private async _initialize() { + await this._rulesControllerService + .downloadRules(this._appStateService.activeDossierTemplateId) + .toPromise() + .then( + rules => { + this.currentLines = this.initialLines = rules.rules.split('\n'); + this.revert(); + }, + () => (this.processing = false) + ); } } diff --git a/apps/red-ui/src/app/modules/admin/screens/smtp-config/smtp-config-screen.component.ts b/apps/red-ui/src/app/modules/admin/screens/smtp-config/smtp-config-screen.component.ts index b36206694..33cef2580 100644 --- a/apps/red-ui/src/app/modules/admin/screens/smtp-config/smtp-config-screen.component.ts +++ b/apps/red-ui/src/app/modules/admin/screens/smtp-config/smtp-config-screen.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnDestroy, OnInit } from '@angular/core'; import { PermissionsService } from '@services/permissions.service'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { AdminDialogService } from '../../services/admin-dialog.service'; @@ -8,15 +8,15 @@ import { SmtpConfigurationControllerService, SMTPConfigurationModel } from '@redaction/red-ui-http'; -import { NotificationService, NotificationType } from '@services/notification.service'; -import { TranslateService } from '@ngx-translate/core'; +import { Toaster } from '../../../../services/toaster.service'; +import { AutoUnsubscribeComponent } from '../../../shared/base/auto-unsubscribe.component'; @Component({ selector: 'redaction-smtp-config-screen', templateUrl: './smtp-config-screen.component.html', styleUrls: ['./smtp-config-screen.component.scss'] }) -export class SmtpConfigScreenComponent implements OnInit { +export class SmtpConfigScreenComponent extends AutoUnsubscribeComponent implements OnInit, OnDestroy { viewReady = false; configForm: FormGroup; generalSettings: GeneralConfigurationModel = { @@ -31,11 +31,11 @@ export class SmtpConfigScreenComponent implements OnInit { private readonly _smtpConfigService: SmtpConfigurationControllerService, private readonly _formBuilder: FormBuilder, private readonly _dialogService: AdminDialogService, - private readonly _notificationService: NotificationService, - private readonly _translateService: TranslateService, + private readonly _toaster: Toaster, private readonly _generalSettingsControllerService: GeneralSettingsControllerService ) { - this.configForm = this._formBuilder.group({ + super(); + this.configForm = _formBuilder.group({ host: [undefined, Validators.required], port: [25], from: [undefined, [Validators.required, Validators.email]], @@ -50,7 +50,7 @@ export class SmtpConfigScreenComponent implements OnInit { password: [undefined] }); - this.configForm.controls.auth.valueChanges.subscribe(auth => { + this.addSubscription = this.configForm.controls.auth.valueChanges.subscribe(auth => { if (auth) { this.openAuthConfigDialog(); } @@ -110,17 +110,9 @@ export class SmtpConfigScreenComponent implements OnInit { this.viewReady = false; try { await this._smtpConfigService.testSMTPConfiguration(this.configForm.getRawValue()).toPromise(); - this._notificationService.showToastNotification( - this._translateService.instant('smtp-config-screen.test.success'), - undefined, - NotificationType.SUCCESS - ); + this._toaster.success('smtp-config-screen.test.success'); } catch (e) { - this._notificationService.showToastNotification( - this._translateService.instant('smtp-config-screen.test.error'), - undefined, - NotificationType.ERROR - ); + this._toaster.error('smtp-config-screen.test.error'); } finally { this.viewReady = true; } diff --git a/apps/red-ui/src/app/modules/admin/screens/watermark/watermark-screen.component.ts b/apps/red-ui/src/app/modules/admin/screens/watermark/watermark-screen.component.ts index 3bc64a4da..627166a6e 100644 --- a/apps/red-ui/src/app/modules/admin/screens/watermark/watermark-screen.component.ts +++ b/apps/red-ui/src/app/modules/admin/screens/watermark/watermark-screen.component.ts @@ -7,8 +7,7 @@ import { HttpClient } from '@angular/common/http'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { debounce } from '@utils/debounce'; import { WatermarkControllerService, WatermarkModelRes } from '@redaction/red-ui-http'; -import { NotificationService, NotificationType } from '@services/notification.service'; -import { TranslateService } from '@ngx-translate/core'; +import { Toaster } from '../../../../services/toaster.service'; import { ActivatedRoute } from '@angular/router'; import { BASE_HREF } from '../../../../tokens'; import { stampPDFPage } from '../../../../utils/page-stamper'; @@ -39,15 +38,14 @@ export class WatermarkScreenComponent implements OnInit { readonly permissionsService: PermissionsService, readonly appStateService: AppStateService, @Inject(BASE_HREF) private readonly _baseHref: string, - private readonly _translateService: TranslateService, private readonly _watermarkControllerService: WatermarkControllerService, - private readonly _notificationService: NotificationService, + private readonly _toaster: Toaster, private readonly _http: HttpClient, private readonly _changeDetectorRef: ChangeDetectorRef, private readonly _formBuilder: FormBuilder, private readonly _activatedRoute: ActivatedRoute ) { - this.appStateService.activateDossierTemplate(_activatedRoute.snapshot.params.dossierTemplateId); + appStateService.activateDossierTemplate(_activatedRoute.snapshot.params.dossierTemplateId); this._initForm(); } @@ -81,24 +79,12 @@ export class WatermarkScreenComponent implements OnInit { ? this._watermarkControllerService.saveWatermark(watermark, this.appStateService.activeDossierTemplateId) : this._watermarkControllerService.deleteWatermark(this.appStateService.activeDossierTemplateId); - observable.subscribe( + observable.toPromise().then( () => { this._loadWatermark(); - this._notificationService.showToastNotification( - this._translateService.instant( - watermark.text ? 'watermark-screen.action.change-success' : 'watermark-screen.action.delete-success' - ), - null, - NotificationType.SUCCESS - ); + this._toaster.success(watermark.text ? 'watermark-screen.action.change-success' : 'watermark-screen.action.delete-success'); }, - () => { - this._notificationService.showToastNotification( - this._translateService.instant('watermark-screen.action.error'), - null, - NotificationType.ERROR - ); - } + () => this._toaster.error('watermark-screen.action.error') ); } @@ -188,11 +174,41 @@ export class WatermarkScreenComponent implements OnInit { private _initForm() { this.configForm = this._formBuilder.group({ text: [{ value: null, disabled: !this.permissionsService.isAdmin() }], - hexColor: [{ value: null, disabled: !this.permissionsService.isAdmin() }, Validators.required], - opacity: [{ value: null, disabled: !this.permissionsService.isAdmin() }, Validators.required], - fontSize: [{ value: null, disabled: !this.permissionsService.isAdmin() }, Validators.required], - fontType: [{ value: null, disabled: !this.permissionsService.isAdmin() }, Validators.required], - orientation: [{ value: null, disabled: !this.permissionsService.isAdmin() }, Validators.required] + hexColor: [ + { + value: null, + disabled: !this.permissionsService.isAdmin() + }, + Validators.required + ], + opacity: [ + { + value: null, + disabled: !this.permissionsService.isAdmin() + }, + Validators.required + ], + fontSize: [ + { + value: null, + disabled: !this.permissionsService.isAdmin() + }, + Validators.required + ], + fontType: [ + { + value: null, + disabled: !this.permissionsService.isAdmin() + }, + Validators.required + ], + orientation: [ + { + value: null, + disabled: !this.permissionsService.isAdmin() + }, + Validators.required + ] }); } } 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 39de289e9..f212e5115 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 @@ -7,9 +7,9 @@ import { TranslateChartService } from '@services/translate-chart.service'; import { StatusSorter } from '@utils/sorters/status-sorter'; 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'; +import { Toaster } from '../../../../services/toaster.service'; +import { FilterService } from '../../../shared/services/filter.service'; +import { FileStatusWrapper } from '../../../../models/file/file-status.wrapper'; import { DossierAttributeWithValue } from '@models/dossier-attributes.model'; @Component({ @@ -34,7 +34,7 @@ export class DossierDetailsComponent implements OnInit { readonly filterService: FilterService, private readonly _changeDetectorRef: ChangeDetectorRef, private readonly _userService: UserService, - private readonly _notificationService: NotificationService + private readonly _toaster: Toaster ) {} get memberIds(): string[] { @@ -86,6 +86,6 @@ export class DossierDetailsComponent implements OnInit { const ownerName = this._userService.getNameForId(this.owner.userId); const dossierName = this.appStateService.activeDossier.name; const msg = 'Successfully assigned ' + ownerName + ' to dossier: ' + dossierName; - this._notificationService.showToastNotification(msg); + this._toaster.info(msg); } } diff --git a/apps/red-ui/src/app/modules/dossier/components/page-exclusion/page-exclusion.component.ts b/apps/red-ui/src/app/modules/dossier/components/page-exclusion/page-exclusion.component.ts index 7245e5ccc..0dd8eb2bb 100644 --- a/apps/red-ui/src/app/modules/dossier/components/page-exclusion/page-exclusion.component.ts +++ b/apps/red-ui/src/app/modules/dossier/components/page-exclusion/page-exclusion.component.ts @@ -2,9 +2,9 @@ import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from import { PermissionsService } from '@services/permissions.service'; import { FormBuilder, FormGroup } from '@angular/forms'; import { PageRange, ReanalysisControllerService } from '@redaction/red-ui-http'; -import { FileDataModel } from '@models/file/file-data.model'; -import { NotificationService, NotificationType } from '@services/notification.service'; -import { LoadingService } from '@services/loading.service'; +import { FileDataModel } from '../../../../models/file/file-data.model'; +import { Toaster } from '../../../../services/toaster.service'; +import { LoadingService } from '../../../../services/loading.service'; import { TranslateService } from '@ngx-translate/core'; @Component({ @@ -23,9 +23,8 @@ export class PageExclusionComponent implements OnChanges { readonly permissionsService: PermissionsService, private readonly _formBuilder: FormBuilder, private readonly _reanalysisControllerService: ReanalysisControllerService, - private readonly _notificationService: NotificationService, - private readonly _loadingService: LoadingService, - private readonly _translateService: TranslateService + private readonly _toaster: Toaster, + private readonly _loadingService: LoadingService ) { this.excludePagesForm = this._formBuilder.group({ value: [''] @@ -80,11 +79,7 @@ export class PageExclusionComponent implements OnChanges { this.excludePagesForm.reset(); this.actionPerformed.emit('exclude-pages'); } catch (e) { - this._notificationService.showToastNotification( - this._translateService.instant('file-preview.tabs.exclude-pages.error'), - null, - NotificationType.ERROR - ); + this._toaster.error('file-preview.tabs.exclude-pages.error'); this._loadingService.stop(); } } diff --git a/apps/red-ui/src/app/modules/dossier/components/team-members-manager/team-members-manager.component.ts b/apps/red-ui/src/app/modules/dossier/components/team-members-manager/team-members-manager.component.ts index 85e32b0d4..665fa19c8 100644 --- a/apps/red-ui/src/app/modules/dossier/components/team-members-manager/team-members-manager.component.ts +++ b/apps/red-ui/src/app/modules/dossier/components/team-members-manager/team-members-manager.component.ts @@ -1,8 +1,8 @@ import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; -import { Dossier, DossierControllerService, StatusControllerService } from '@redaction/red-ui-http'; +import { Dossier } from '@redaction/red-ui-http'; import { AppStateService } from '@state/app-state.service'; import { UserService } from '@services/user.service'; -import { NotificationService, NotificationType } from '@services/notification.service'; +import { Toaster } from '../../../../services/toaster.service'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { DossierWrapper } from '@state/model/dossier.wrapper'; @@ -21,10 +21,8 @@ export class TeamMembersManagerComponent implements OnInit { constructor( readonly userService: UserService, - private readonly _dossierControllerService: DossierControllerService, - private readonly _notificationService: NotificationService, + private readonly _toaster: Toaster, private readonly _formBuilder: FormBuilder, - private readonly _statusControllerService: StatusControllerService, private readonly _appStateService: AppStateService ) {} @@ -91,11 +89,7 @@ export class TeamMembersManagerComponent implements OnInit { result = await this._appStateService.createOrUpdateDossier(dw.dossier); this.save.emit(result); } catch (error) { - this._notificationService.showToastNotification( - 'Failed: ' + error.error ? error.error.message : error, - null, - NotificationType.ERROR - ); + this._toaster.error('Failed: ' + error.error ? error.error.message : error); } } diff --git a/apps/red-ui/src/app/modules/dossier/dialogs/assign-reviewer-approver-dialog/assign-reviewer-approver-dialog.component.ts b/apps/red-ui/src/app/modules/dossier/dialogs/assign-reviewer-approver-dialog/assign-reviewer-approver-dialog.component.ts index 0c7390409..b82a86f1d 100644 --- a/apps/red-ui/src/app/modules/dossier/dialogs/assign-reviewer-approver-dialog/assign-reviewer-approver-dialog.component.ts +++ b/apps/red-ui/src/app/modules/dossier/dialogs/assign-reviewer-approver-dialog/assign-reviewer-approver-dialog.component.ts @@ -1,9 +1,9 @@ import { Component, Inject } from '@angular/core'; -import { DossierControllerService, StatusControllerService } from '@redaction/red-ui-http'; +import { StatusControllerService } from '@redaction/red-ui-http'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { AppStateService } from '@state/app-state.service'; import { UserService } from '@services/user.service'; -import { NotificationService, NotificationType } from '@services/notification.service'; +import { Toaster } from '../../../../services/toaster.service'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { FileStatusWrapper } from '@models/file/file-status.wrapper'; import { DossierWrapper } from '@state/model/dossier.wrapper'; @@ -16,7 +16,6 @@ class DialogData { } @Component({ - selector: 'redaction-dossier-details-dialog', templateUrl: './assign-reviewer-approver-dialog.component.html', styleUrls: ['./assign-reviewer-approver-dialog.component.scss'] }) @@ -26,13 +25,12 @@ export class AssignReviewerApproverDialogComponent { constructor( readonly userService: UserService, - private readonly _dossierControllerService: DossierControllerService, - private readonly _notificationService: NotificationService, + private readonly _toaster: Toaster, private readonly _formBuilder: FormBuilder, private readonly _statusControllerService: StatusControllerService, private readonly _appStateService: AppStateService, - public dialogRef: MatDialogRef, - @Inject(MAT_DIALOG_DATA) public data: DialogData + private readonly _dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) readonly data: DialogData ) { this._loadData(); } @@ -52,10 +50,8 @@ export class AssignReviewerApproverDialogComponent { return true; } - const reviewerId = this.selectedSingleUser; - for (const file of this.data.files) { - if (file.currentReviewer !== reviewerId) { + if (file.currentReviewer !== this.selectedSingleUser) { return true; } } @@ -94,14 +90,10 @@ export class AssignReviewerApproverDialogComponent { file.reviewerName = this.userService.getNameForId(selectedUser); } } catch (error) { - this._notificationService.showToastNotification( - 'Failed: ' + error.error ? error.error.message : error, - null, - NotificationType.ERROR - ); + this._toaster.error('Failed: ' + error.error ? error.error.message : error); } - this.dialogRef.close(); + this._dialogRef.close(); } private _loadData() { diff --git a/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/edit-dossier-dialog.component.ts b/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/edit-dossier-dialog.component.ts index 99c5b12d9..7e08320d3 100644 --- a/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/edit-dossier-dialog.component.ts +++ b/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/edit-dossier-dialog.component.ts @@ -1,13 +1,10 @@ import { ChangeDetectorRef, Component, Inject, ViewChild } from '@angular/core'; -import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; -import { FormBuilder } from '@angular/forms'; -import { AppStateService } from '@state/app-state.service'; +import { MAT_DIALOG_DATA } from '@angular/material/dialog'; import { DossierWrapper } from '../../../../state/model/dossier.wrapper'; import { EditDossierGeneralInfoComponent } from './general-info/edit-dossier-general-info.component'; import { EditDossierDownloadPackageComponent } from './download-package/edit-dossier-download-package.component'; import { EditDossierSectionInterface } from './edit-dossier-section.interface'; -import { NotificationService, NotificationType } from '@services/notification.service'; -import { TranslateService } from '@ngx-translate/core'; +import { Toaster } from '../../../../services/toaster.service'; import { EditDossierDictionaryComponent } from './dictionary/edit-dossier-dictionary.component'; import { EditDossierTeamMembersComponent } from './team-members/edit-dossier-team-members.component'; import { EditDossierAttributesComponent } from './attributes/edit-dossier-attributes.component'; @@ -38,12 +35,8 @@ export class EditDossierDialogComponent { @ViewChild(EditDossierAttributesComponent) attributesComponent: EditDossierAttributesComponent; constructor( - private readonly _appStateService: AppStateService, - private readonly _formBuilder: FormBuilder, - private readonly _notificationService: NotificationService, - private readonly _translateService: TranslateService, + private readonly _toaster: Toaster, private readonly _changeRef: ChangeDetectorRef, - private readonly _dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) private readonly _data: { dossierWrapper: DossierWrapper; @@ -82,11 +75,7 @@ export class EditDossierDialogComponent { } updatedDossier(updatedDossier: DossierWrapper) { - this._notificationService.showToastNotification( - this._translateService.instant('edit-dossier-dialog.change-successful'), - null, - NotificationType.SUCCESS - ); + this._toaster.success('edit-dossier-dialog.change-successful'); if (updatedDossier) { this.dossierWrapper = updatedDossier; @@ -111,11 +100,7 @@ export class EditDossierDialogComponent { changeTab(key: Section) { if (this.activeComponent.changed) { - this._notificationService.showToastNotification( - this._translateService.instant('edit-dossier-dialog.unsaved-changes'), - null, - NotificationType.ERROR - ); + this._toaster.error('edit-dossier-dialog.unsaved-changes'); return; } this.activeNav = key; diff --git a/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/general-info/edit-dossier-general-info.component.ts b/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/general-info/edit-dossier-general-info.component.ts index 3fd26d42d..dc52079f7 100644 --- a/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/general-info/edit-dossier-general-info.component.ts +++ b/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/general-info/edit-dossier-general-info.component.ts @@ -10,8 +10,7 @@ import { PermissionsService } from '@services/permissions.service'; import { Router } from '@angular/router'; import { MatDialogRef } from '@angular/material/dialog'; import { EditDossierDialogComponent } from '../edit-dossier-dialog.component'; -import { NotificationService, NotificationType } from '../../../../../services/notification.service'; -import { TranslateService } from '@ngx-translate/core'; +import { Toaster } from '../../../../../services/toaster.service'; @Component({ selector: 'redaction-edit-dossier-general-info', @@ -34,8 +33,7 @@ export class EditDossierGeneralInfoComponent implements OnInit, EditDossierSecti private readonly _dialogService: DossiersDialogService, private readonly _router: Router, private readonly _editDossierDialogRef: MatDialogRef, - private readonly _notificationService: NotificationService, - private readonly _translateService: TranslateService + private readonly _toaster: Toaster ) {} get changed() { @@ -113,11 +111,7 @@ export class EditDossierGeneralInfoComponent implements OnInit, EditDossierSecti } private _notifyDossierDeleted() { - this._notificationService.showToastNotification( - this._translateService.instant('edit-dossier-dialog.delete-successful'), - null, - NotificationType.SUCCESS - ); + this._toaster.success('edit-dossier-dialog.delete-successful'); } private _filterInvalidDossierTemplates() { diff --git a/apps/red-ui/src/app/modules/dossier/dialogs/force-redaction-dialog/force-redaction-dialog.component.ts b/apps/red-ui/src/app/modules/dossier/dialogs/force-redaction-dialog/force-redaction-dialog.component.ts index 8b905a9a1..c3de99226 100644 --- a/apps/red-ui/src/app/modules/dossier/dialogs/force-redaction-dialog/force-redaction-dialog.component.ts +++ b/apps/red-ui/src/app/modules/dossier/dialogs/force-redaction-dialog/force-redaction-dialog.component.ts @@ -3,7 +3,7 @@ import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { AppStateService } from '@state/app-state.service'; import { MatDialogRef } from '@angular/material/dialog'; import { ForceRedactionRequest, LegalBasisMappingControllerService } from '@redaction/red-ui-http'; -import { NotificationService } from '@services/notification.service'; +import { Toaster } from '../../../../services/toaster.service'; import { TranslateService } from '@ngx-translate/core'; import { UserService } from '@services/user.service'; import { ManualAnnotationService } from '../../services/manual-annotation.service'; @@ -29,7 +29,7 @@ export class ForceRedactionDialogComponent implements OnInit { private readonly _appStateService: AppStateService, private readonly _userService: UserService, private readonly _formBuilder: FormBuilder, - private readonly _notificationService: NotificationService, + private readonly _notificationService: Toaster, private readonly _translateService: TranslateService, private readonly _legalBasisMappingControllerService: LegalBasisMappingControllerService, private readonly _manualAnnotationService: ManualAnnotationService, diff --git a/apps/red-ui/src/app/modules/dossier/dialogs/manual-redaction-dialog/manual-annotation-dialog.component.ts b/apps/red-ui/src/app/modules/dossier/dialogs/manual-redaction-dialog/manual-annotation-dialog.component.ts index 85fe5c5e1..b5c5d24df 100644 --- a/apps/red-ui/src/app/modules/dossier/dialogs/manual-redaction-dialog/manual-annotation-dialog.component.ts +++ b/apps/red-ui/src/app/modules/dossier/dialogs/manual-redaction-dialog/manual-annotation-dialog.component.ts @@ -3,7 +3,7 @@ import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { AppStateService } from '@state/app-state.service'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { AddRedactionRequest, LegalBasisMappingControllerService } from '@redaction/red-ui-http'; -import { NotificationService } from '@services/notification.service'; +import { Toaster } from '../../../../services/toaster.service'; import { TranslateService } from '@ngx-translate/core'; import { UserService } from '@services/user.service'; import { ManualRedactionEntryWrapper } from '@models/file/manual-redaction-entry.wrapper'; @@ -37,7 +37,7 @@ export class ManualAnnotationDialogComponent implements OnInit { private readonly _appStateService: AppStateService, private readonly _userService: UserService, private readonly _formBuilder: FormBuilder, - private readonly _notificationService: NotificationService, + private readonly _notificationService: Toaster, private readonly _translateService: TranslateService, private readonly _legalBasisMappingControllerService: LegalBasisMappingControllerService, private readonly _manualAnnotationService: ManualAnnotationService, 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 88c1161d3..33068bc48 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,6 +1,6 @@ import { ChangeDetectorRef, Component, ElementRef, HostListener, Injector, OnDestroy, OnInit, TemplateRef, ViewChild } from '@angular/core'; import { NavigationStart, Router } from '@angular/router'; -import { NotificationService, NotificationType } from '@services/notification.service'; +import { Toaster } from '../../../../services/toaster.service'; import { AppStateService } from '@state/app-state.service'; import { FileDropOverlayService } from '@upload-download/services/file-drop-overlay.service'; import { FileUploadModel } from '@upload-download/model/file-upload.model'; @@ -59,7 +59,7 @@ export class DossierOverviewScreenComponent constructor( readonly permissionsService: PermissionsService, private readonly _userService: UserService, - private readonly _notificationService: NotificationService, + private readonly _toaster: Toaster, private readonly _dialogService: DossiersDialogService, private readonly _fileUploadService: FileUploadService, private readonly _statusOverlayService: StatusOverlayService, @@ -149,19 +149,9 @@ export class DossierOverviewScreenComponent .reanalyzeDossier() .then(() => { this.reloadDossiers(); - this._notificationService.showToastNotification( - this._translateService.instant('dossier-overview.reanalyse-dossier.success'), - null, - NotificationType.SUCCESS - ); + this._toaster.success('dossier-overview.reanalyse-dossier.success'); }) - .catch(() => { - this._notificationService.showToastNotification( - this._translateService.instant('dossier-overview.reanalyse-dossier.error'), - null, - NotificationType.ERROR - ); - }); + .catch(() => this._toaster.error('dossier-overview.reanalyse-dossier.error')); } reloadDossiers() { diff --git a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/file-preview-screen.component.ts b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/file-preview-screen.component.ts index 15c433e57..bb0d1f886 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/file-preview-screen.component.ts +++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/file-preview-screen.component.ts @@ -12,8 +12,7 @@ import { AnnotationData, FileDataModel } from '@models/file/file-data.model'; import { FileActionService } from '../../services/file-action.service'; import { AnnotationDrawService } from '../../services/annotation-draw.service'; import { AnnotationProcessingService } from '../../services/annotation-processing.service'; -import { tap } from 'rxjs/operators'; -import { NotificationService } from '@services/notification.service'; +import { Toaster } from '../../../../services/toaster.service'; import { FileStatusWrapper } from '@models/file/file-status.wrapper'; import { PermissionsService } from '@services/permissions.service'; import { Subscription, timer } from 'rxjs'; @@ -37,6 +36,7 @@ import { handleFilterDelta, processFilters } from '@shared/components/filters/po import { LoadingService } from '../../../../services/loading.service'; import { stampPDFPage } from '../../../../utils/page-stamper'; import { TranslateService } from '@ngx-translate/core'; +import { AutoUnsubscribeComponent } from '../../../shared/base/auto-unsubscribe.component'; const ALL_HOTKEY_ARRAY = ['Escape', 'F', 'f']; @@ -45,7 +45,7 @@ const ALL_HOTKEY_ARRAY = ['Escape', 'F', 'f']; templateUrl: './file-preview-screen.component.html', styleUrls: ['./file-preview-screen.component.scss'] }) -export class FilePreviewScreenComponent implements OnInit, OnDestroy, OnAttach, OnDetach { +export class FilePreviewScreenComponent extends AutoUnsubscribeComponent implements OnInit, OnDestroy, OnAttach, OnDetach { dialogRef: MatDialogRef; viewMode: ViewMode = 'STANDARD'; fullScreen = false; @@ -59,7 +59,6 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy, OnAttach, secondaryFilters: FilterModel[]; canPerformAnnotationActions: boolean; filesAutoUpdateTimer: Subscription; - fileReanalysedSubscription: Subscription; hideSkipped = false; displayPDFViewer = false; viewDocumentInfo = false; @@ -81,7 +80,7 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy, OnAttach, private readonly _activatedRoute: ActivatedRoute, private readonly _dialogService: DossiersDialogService, private readonly _router: Router, - private readonly _notificationService: NotificationService, + private readonly _toaster: Toaster, private readonly _annotationProcessingService: AnnotationProcessingService, private readonly _annotationDrawService: AnnotationDrawService, private readonly _fileActionService: FileActionService, @@ -92,6 +91,7 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy, OnAttach, private readonly _loadingService: LoadingService, private readonly _translateService: TranslateService ) { + super(); document.documentElement.addEventListener('fullscreenchange', () => { if (!document.fullscreenElement) { this.fullScreen = false; @@ -221,7 +221,7 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy, OnAttach, ngOnDetach() { this.displayPDFViewer = false; this.viewReady = false; - this._unsubscribeFromFileUpdates(); + super.ngOnDestroy(); } async ngOnAttach(previousRoute: ActivatedRouteSnapshot) { @@ -249,10 +249,6 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy, OnAttach, this.viewReady = true; } - ngOnDestroy(): void { - this._unsubscribeFromFileUpdates(); - } - rebuildFilters(deletePreviousAnnotations: boolean = false) { const startTime = new Date().getTime(); if (deletePreviousAnnotations) { @@ -469,7 +465,7 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy, OnAttach, await this._statusControllerService.setFileReviewer(dossierId, fileId, reviewerId).toPromise(); const msg = `Successfully assigned ${reviewerName} to file: ${filename}`; - this._notificationService.showToastNotification(msg); + this._toaster.info(msg); await this.appStateService.reloadActiveFile(); this._updateCanPerformActions(); this.editingReviewer = false; @@ -488,7 +484,7 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy, OnAttach, } downloadOriginalFile() { - this._fileManagementControllerService + this.addSubscription = this._fileManagementControllerService .downloadOriginalFile(this.dossierId, this.fileId, true, this.fileData.fileStatus.cacheIdentifier, 'response') .subscribe(data => { download(data, this.fileData.fileStatus.filename); @@ -539,10 +535,8 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy, OnAttach, } private _subscribeToFileUpdates(): void { - this.filesAutoUpdateTimer = timer(0, 5000) - .pipe(tap(async () => await this.appStateService.reloadActiveFile())) - .subscribe(); - this.fileReanalysedSubscription = this.appStateService.fileReanalysed.subscribe(async (fileStatus: FileStatusWrapper) => { + this.addSubscription = timer(0, 5000).subscribe(async () => await this.appStateService.reloadActiveFile()); + this.addSubscription = this.appStateService.fileReanalysed.subscribe(async (fileStatus: FileStatusWrapper) => { if (fileStatus.fileId === this.fileId) { await this._loadFileData(!this._reloadFileOnReanalysis); this._reloadFileOnReanalysis = false; @@ -553,11 +547,6 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy, OnAttach, }); } - private _unsubscribeFromFileUpdates(): void { - this.filesAutoUpdateTimer.unsubscribe(); - this.fileReanalysedSubscription.unsubscribe(); - } - private _updateCanPerformActions() { this.canPerformAnnotationActions = this.permissionsService.canPerformAnnotationActions() && diff --git a/apps/red-ui/src/app/modules/dossier/services/manual-annotation.service.ts b/apps/red-ui/src/app/modules/dossier/services/manual-annotation.service.ts index 055e3670d..102baff26 100644 --- a/apps/red-ui/src/app/modules/dossier/services/manual-annotation.service.ts +++ b/apps/red-ui/src/app/modules/dossier/services/manual-annotation.service.ts @@ -7,7 +7,7 @@ import { ManualRedactionControllerService } from '@redaction/red-ui-http'; import { AnnotationWrapper } from '@models/file/annotation.wrapper'; -import { NotificationService, NotificationType } from '@services/notification.service'; +import { Toaster } from '../../../services/toaster.service'; import { TranslateService } from '@ngx-translate/core'; import { tap } from 'rxjs/operators'; import { UserService } from '@services/user.service'; @@ -38,7 +38,7 @@ export class ManualAnnotationService { private readonly _appStateService: AppStateService, private readonly _userService: UserService, private readonly _translateService: TranslateService, - private readonly _notificationService: NotificationService, + private readonly _toaster: Toaster, private readonly _manualRedactionControllerService: ManualRedactionControllerService, private readonly _dictionaryControllerService: DictionaryControllerService, private readonly _permissionsService: PermissionsService @@ -76,8 +76,12 @@ export class ManualAnnotationService { return obs.pipe( tap( - () => this._notify(this._getMessage(mode)), - error => this._notify(this._getMessage(mode, modifyDictionary, true), NotificationType.ERROR, error) + () => this._toaster.success(this._getMessage(mode), { positionClass: 'toast-file-preview' }), + error => + this._toaster.error(this._getMessage(mode, modifyDictionary, true), { + params: error, + positionClass: 'toast-file-preview' + }) ) ); } @@ -223,13 +227,6 @@ export class ManualAnnotationService { } } - private _notify(key: string, type: NotificationType = NotificationType.SUCCESS, data?: any) { - this._notificationService.showToastNotification(this._translateService.instant(key, data), null, type, { - positionClass: 'toast-file-preview', - actions: [] - }); - } - private _getMessage(mode: Mode, modifyDictionary?: boolean, error: boolean = false) { return ( 'annotation-actions.message.' + diff --git a/apps/red-ui/src/app/modules/shared/base/auto-unsubscribe.component.ts b/apps/red-ui/src/app/modules/shared/base/auto-unsubscribe.component.ts index 680769e31..761726bf7 100644 --- a/apps/red-ui/src/app/modules/shared/base/auto-unsubscribe.component.ts +++ b/apps/red-ui/src/app/modules/shared/base/auto-unsubscribe.component.ts @@ -3,8 +3,6 @@ import { Subscription } from 'rxjs'; /** * Inherit this class when you need to subscribe to observables in your components - * - * @remarks You must explicitly call super.ngOnDestroy in you component to unsubscribe */ @Component({ template: '' }) export abstract class AutoUnsubscribeComponent implements OnDestroy { @@ -21,7 +19,8 @@ export abstract class AutoUnsubscribeComponent implements OnDestroy { /** * This method unsubscribes active subscriptions - * Explicitly call this method in your component's ngOnDestroy + * If you implement OnDestroy in a component that inherits AutoUnsubscribeComponent, + * then you must explicitly call super.ngOnDestroy() */ ngOnDestroy(): void { this._subscriptions.unsubscribe(); 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 7ba2a27aa..201189b97 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 @@ -5,7 +5,6 @@ import { FilterService } from '../services/filter.service'; import { SearchService } from '../services/search.service'; import { ScreenStateService } from '../services/screen-state.service'; import { Observable } from 'rxjs'; -import { FilterModel } from '../components/filters/popup-filter/model/filter.model'; import { AutoUnsubscribeComponent } from './auto-unsubscribe.component'; @Component({ template: '' }) diff --git a/apps/red-ui/src/app/modules/shared/components/buttons/file-download-btn/file-download-btn.component.ts b/apps/red-ui/src/app/modules/shared/components/buttons/file-download-btn/file-download-btn.component.ts index 3233c7e4e..0ec7be010 100644 --- a/apps/red-ui/src/app/modules/shared/components/buttons/file-download-btn/file-download-btn.component.ts +++ b/apps/red-ui/src/app/modules/shared/components/buttons/file-download-btn/file-download-btn.component.ts @@ -1,11 +1,11 @@ -import { ChangeDetectionStrategy, Component, Inject, Input } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Inject, Input, OnDestroy } from '@angular/core'; import { PermissionsService } from '@services/permissions.service'; import { DossierWrapper } from '@state/model/dossier.wrapper'; import { FileStatusWrapper } from '@models/file/file-status.wrapper'; import { FileDownloadService } from '@upload-download/services/file-download.service'; -import { NotificationService } from '@services/notification.service'; -import { TranslateService } from '@ngx-translate/core'; +import { Toaster } from '@services/toaster.service'; import { BASE_HREF } from '../../../../../tokens'; +import { AutoUnsubscribeComponent } from '@shared/base/auto-unsubscribe.component'; export type MenuState = 'OPEN' | 'CLOSED'; @@ -15,7 +15,7 @@ export type MenuState = 'OPEN' | 'CLOSED'; styleUrls: ['./file-download-btn.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush }) -export class FileDownloadBtnComponent { +export class FileDownloadBtnComponent extends AutoUnsubscribeComponent implements OnDestroy { @Input() dossier: DossierWrapper; @Input() file: FileStatusWrapper | FileStatusWrapper[]; @Input() tooltipPosition: 'above' | 'below' | 'before' | 'after' = 'above'; @@ -27,9 +27,10 @@ export class FileDownloadBtnComponent { @Inject(BASE_HREF) private readonly _baseHref: string, private readonly _permissionsService: PermissionsService, private readonly _fileDownloadService: FileDownloadService, - private readonly _translateService: TranslateService, - private readonly _notificationService: NotificationService - ) {} + private readonly _toaster: Toaster + ) { + super(); + } get canDownloadFiles() { if (!Array.isArray(this.file)) { @@ -41,12 +42,10 @@ export class FileDownloadBtnComponent { downloadFiles($event: MouseEvent) { $event.stopPropagation(); - this._fileDownloadService.downloadFiles(Array.isArray(this.file) ? this.file : [this.file], this.dossier).subscribe(() => { - this._notificationService.showToastNotification( - this._translateService.instant('download-status.queued', { - baseUrl: this._baseHref - }) - ); - }); + this.addSubscription = this._fileDownloadService + .downloadFiles(Array.isArray(this.file) ? this.file : [this.file], this.dossier) + .subscribe(() => { + this._toaster.info('download-status.queued', { params: { baseUrl: this._baseHref } }); + }); } } diff --git a/apps/red-ui/src/app/modules/shared/services/dictionary-save.service.ts b/apps/red-ui/src/app/modules/shared/services/dictionary-save.service.ts index 4f6886952..5bce425e3 100644 --- a/apps/red-ui/src/app/modules/shared/services/dictionary-save.service.ts +++ b/apps/red-ui/src/app/modules/shared/services/dictionary-save.service.ts @@ -1,7 +1,6 @@ import { Injectable } from '@angular/core'; import { Observable, throwError } from 'rxjs'; -import { NotificationService, NotificationType } from '@services/notification.service'; -import { TranslateService } from '@ngx-translate/core'; +import { Toaster } from '../../../services/toaster.service'; import { DictionaryControllerService } from '@redaction/red-ui-http'; import { tap } from 'rxjs/operators'; @@ -11,11 +10,7 @@ const MIN_WORD_LENGTH = 2; providedIn: 'root' }) export class DictionarySaveService { - constructor( - private readonly _notificationService: NotificationService, - private readonly _translateService: TranslateService, - private readonly _dictionaryControllerService: DictionaryControllerService - ) {} + constructor(private readonly _toaster: Toaster, private readonly _dictionaryControllerService: DictionaryControllerService) {} saveEntries( entries: string[], @@ -44,29 +39,13 @@ export class DictionarySaveService { return obs.pipe( tap( () => { - if (showToast) { - this._notificationService.showToastNotification( - this._translateService.instant('dictionary-overview.success.generic'), - null, - NotificationType.SUCCESS - ); - } + if (showToast) this._toaster.success('dictionary-overview.success.generic'); }, - () => { - this._notificationService.showToastNotification( - this._translateService.instant('dictionary-overview.error.generic'), - null, - NotificationType.ERROR - ); - } + () => this._toaster.error('dictionary-overview.error.generic') ) ); } else { - this._notificationService.showToastNotification( - this._translateService.instant('dictionary-overview.error.entries-too-short'), - null, - NotificationType.ERROR - ); + this._toaster.error('dictionary-overview.error.entries-too-short'); return throwError('Entries too short'); } diff --git a/apps/red-ui/src/app/services/error-message.service.ts b/apps/red-ui/src/app/services/error-message.service.ts index d8d311fb1..68fd90874 100644 --- a/apps/red-ui/src/app/services/error-message.service.ts +++ b/apps/red-ui/src/app/services/error-message.service.ts @@ -8,11 +8,11 @@ import { HttpErrorResponse } from '@angular/common/http'; export class ErrorMessageService { constructor(private readonly _translateService: TranslateService) {} - _parseErrorResponse(err: HttpErrorResponse) { + _parseErrorResponse(err: HttpErrorResponse): string { return err?.error?.message?.includes('message') ? ` ${err.error.message.match('"message":"(.*?)\\"')[1]}` : ''; } - getMessage(err: HttpErrorResponse, defaultMessage: string) { - return this._translateService.instant(defaultMessage) + this._parseErrorResponse(err); + getMessage(error: HttpErrorResponse, defaultMessage: string): string { + return this._translateService.instant(defaultMessage) + this._parseErrorResponse(error); } } diff --git a/apps/red-ui/src/app/services/notification.service.ts b/apps/red-ui/src/app/services/notification.service.ts deleted file mode 100644 index 9431503f7..000000000 --- a/apps/red-ui/src/app/services/notification.service.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { Injectable } from '@angular/core'; -import { ActiveToast, ToastrService } from 'ngx-toastr'; -import { IndividualConfig } from 'ngx-toastr/toastr/toastr-config'; -import { NavigationStart, Router } from '@angular/router'; - -export enum NotificationType { - SUCCESS = 'SUCCESS', - WARNING = 'WARNING', - ERROR = 'ERROR', - INFO = 'INFO' -} - -export class ToastAction { - title: string; - action?: () => any; -} - -@Injectable({ - providedIn: 'root' -}) -export class NotificationService { - constructor(private readonly _toastr: ToastrService, private readonly _router: Router) { - _router.events.subscribe(event => { - if (event instanceof NavigationStart) { - this._toastr.clear(); - } - }); - } - - showToastNotification( - message: string, - title?: string, - notificationType = NotificationType.INFO, - options?: Partial & { actions?: ToastAction[] } - ): ActiveToast { - switch (notificationType) { - case NotificationType.ERROR: - return this._toastr.error(message, title, options); - case NotificationType.SUCCESS: - return this._toastr.success(message, title, options); - case NotificationType.WARNING: - return this._toastr.warning(message, title, options); - case NotificationType.INFO: - return this._toastr.info(message, title, options); - } - } -} diff --git a/apps/red-ui/src/app/services/toaster.service.ts b/apps/red-ui/src/app/services/toaster.service.ts new file mode 100644 index 000000000..c24fe23d0 --- /dev/null +++ b/apps/red-ui/src/app/services/toaster.service.ts @@ -0,0 +1,82 @@ +import { Injectable } from '@angular/core'; +import { ActiveToast, ToastrService } from 'ngx-toastr'; +import { IndividualConfig } from 'ngx-toastr/toastr/toastr-config'; +import { NavigationStart, Router } from '@angular/router'; +import { TranslateService } from '@ngx-translate/core'; +import { HttpErrorResponse } from '@angular/common/http'; +import { ErrorMessageService } from '@services/error-message.service'; +import { filter } from 'rxjs/operators'; + +export enum NotificationType { + SUCCESS = 'SUCCESS', + WARNING = 'WARNING', + INFO = 'INFO' +} + +export interface ToasterOptions extends IndividualConfig { + title?: string; + /** + * These params are used as interpolateParams for translate service + */ + params?: object; + actions?: { title?: string; action: () => void }[]; +} + +export interface ErrorToasterOptions extends ToasterOptions { + /** + * Pass an http error that will be processed by error message service and shown in toast + */ + error?: HttpErrorResponse; +} + +@Injectable({ + providedIn: 'root' +}) +export class Toaster { + constructor( + private readonly _toastr: ToastrService, + private readonly _router: Router, + private readonly _translateService: TranslateService, + private readonly _errorMessageService: ErrorMessageService + ) { + _router.events.pipe(filter(event => event instanceof NavigationStart)).subscribe(() => { + _toastr.clear(); + }); + } + + error(message: string, options?: Partial) { + if (options?.error) message = this._errorMessageService.getMessage(options.error, message); + else message = this._translateService.instant(message, options?.params); + + return this._toastr.error(message, options?.title, options); + } + + info(message: string, options?: Partial) { + return this._showToastNotification(message, NotificationType.INFO, options); + } + + success(message: string, options?: Partial) { + return this._showToastNotification(message, NotificationType.SUCCESS, options); + } + + warning(message: string, options?: Partial) { + return this._showToastNotification(message, NotificationType.WARNING, options); + } + + private _showToastNotification( + message: string, + notificationType = NotificationType.INFO, + options?: Partial + ): ActiveToast { + message = this._translateService.instant(message, options?.params); + + switch (notificationType) { + case NotificationType.SUCCESS: + return this._toastr.success(message, options?.title, options); + case NotificationType.WARNING: + return this._toastr.warning(message, options?.title, options); + case NotificationType.INFO: + return this._toastr.info(message, options?.title, options); + } + } +} diff --git a/apps/red-ui/src/app/state/app-state.service.ts b/apps/red-ui/src/app/state/app-state.service.ts index 8acaa3a7b..ce9146e7d 100644 --- a/apps/red-ui/src/app/state/app-state.service.ts +++ b/apps/red-ui/src/app/state/app-state.service.ts @@ -9,7 +9,7 @@ import { ReanalysisControllerService, StatusControllerService } from '@redaction/red-ui-http'; -import { NotificationService, NotificationType } from '@services/notification.service'; +import { Toaster } from '../services/toaster.service'; import { TranslateService } from '@ngx-translate/core'; import { Event, NavigationEnd, ResolveStart, Router } from '@angular/router'; import { UserService } from '@services/user.service'; @@ -47,7 +47,7 @@ export class AppStateService { private readonly _router: Router, private readonly _userService: UserService, private readonly _dossiersService: DossiersService, - private readonly _notificationService: NotificationService, + private readonly _toaster: Toaster, private readonly _reanalysisControllerService: ReanalysisControllerService, private readonly _translateService: TranslateService, private readonly _dictionaryControllerService: DictionaryControllerService, @@ -239,7 +239,7 @@ export class AppStateService { this.activeDossierTemplateId, this._appState.fileAttributesConfig[this.activeDossierTemplateId] ); - this.activeDossier.files = this.activeDossier.files.map(file => + this.activeDossier.files = this.activeDossier?.files.map(file => file.fileId === activeFileWrapper.fileId ? activeFileWrapper : file ); @@ -330,13 +330,7 @@ export class AppStateService { const index = this.allDossiers.findIndex(p => p.dossierId === dossier.dossierId); this._appState.dossiers.splice(index, 1); }, - () => { - this._notificationService.showToastNotification( - this._translateService.instant('dossiers.delete.delete-failed', dossier), - null, - NotificationType.ERROR - ); - } + () => this._toaster.error('dossiers.delete.delete-failed', { params: dossier }) ); } @@ -353,12 +347,8 @@ export class AppStateService { this._appState.dossiers = [...this._appState.dossiers]; return foundDossier; } catch (error) { - this._notificationService.showToastNotification( - this._translateService.instant( - error.status === 409 ? 'add-dossier-dialog.errors.dossier-already-exists' : 'add-dossier-dialog.errors.generic' - ), - null, - NotificationType.ERROR + this._toaster.error( + error.status === 409 ? 'add-dossier-dialog.errors.dossier-already-exists' : 'add-dossier-dialog.errors.generic' ); } } From 9d927526550cd83222a50e67a015c5eb5027f265 Mon Sep 17 00:00:00 2001 From: Dan Percic Date: Thu, 15 Jul 2021 15:48:36 +0300 Subject: [PATCH 3/6] fix rebase errors, use combined observables for state, remove unused methods --- .../active-fields-listing.component.html | 14 ++-- .../default-colors-screen.component.html | 21 +----- .../dictionary-listing-screen.component.html | 26 +++---- ...r-attributes-listing-screen.component.html | 31 ++++---- ...er-templates-listing-screen.component.html | 25 ++++--- ...e-attributes-listing-screen.component.html | 28 +++---- .../screens/trash/trash-screen.component.html | 24 +++--- .../user-listing-screen.component.html | 14 ++-- .../dossier-listing-screen.component.html | 10 +-- .../dossier-overview-screen.component.html | 25 +++---- .../dossier-overview-screen.component.ts | 2 - .../shared/base/base-listing.component.ts | 46 +++++------- .../empty-state/empty-state.component.html | 3 +- .../empty-state/empty-state.component.ts | 7 +- .../components/sort-pipe/sort-by.pipe.ts | 20 +---- .../shared/services/screen-state.service.ts | 74 +++++++++++++++---- .../src/app/services/sorting.service.ts | 28 ++++++- .../src/app/services/toaster.service.ts | 4 +- apps/red-ui/src/app/services/user.service.ts | 15 ++-- apps/red-ui/src/assets/i18n/en.json | 5 ++ 20 files changed, 220 insertions(+), 202 deletions(-) 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 index 7dc532184..201646c69 100644 --- 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 @@ -2,15 +2,15 @@
- {{ 'file-attributes-csv-import.table-header.title' | translate: { length: (allEntities$ | async)?.length } }} + {{ 'file-attributes-csv-import.table-header.title' | translate: { length: (screenStateService.allEntitiesLength$ | async) } }} - +
- + 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 a2c268aa8..0269cc400 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 @@ -22,10 +22,7 @@
- {{ - 'default-colors-screen.table-header.title' - | translate: { length: (allEntities$ | async).length } - }} + {{ 'default-colors-screen.table-header.title' | translate: { length: screenStateService.allEntitiesLength$ } }}
@@ -50,25 +47,15 @@
-
+
-
+
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 b0b8c30a4..bce3dd9d8 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 @@ -24,18 +24,18 @@
- {{ 'dictionary-listing.table-header.title' | translate: { length: (displayedEntities$ | async)?.length } }} + {{ 'dictionary-listing.table-header.title' | translate: { length: (screenStateService.displayedLength$ | async) } }}
-
+
- + -
+
@@ -162,7 +154,7 @@
-
+
- {{ 'dossier-attributes-listing.table-header.title' | translate: { length: (displayedEntities$ | async)?.length } }} + {{ + 'dossier-attributes-listing.table-header.title' + | translate: { length: (screenStateService.displayedLength$ | async) } + }}
-
+
- + -
+
@@ -117,15 +117,14 @@ icon="red:edit" tooltip="dossier-attributes-listing.action.edit" type="dark-bg" - > - + > + - + >
diff --git a/apps/red-ui/src/app/modules/admin/screens/dossier-template-listing/dossier-templates-listing-screen.component.html b/apps/red-ui/src/app/modules/admin/screens/dossier-template-listing/dossier-templates-listing-screen.component.html index 1afdd7a3a..5c63de968 100644 --- a/apps/red-ui/src/app/modules/admin/screens/dossier-template-listing/dossier-templates-listing-screen.component.html +++ b/apps/red-ui/src/app/modules/admin/screens/dossier-template-listing/dossier-templates-listing-screen.component.html @@ -22,16 +22,19 @@
- {{ 'dossier-templates-listing.table-header.title' | translate: { length: (displayedEntities$ | async).length } }} + {{ + 'dossier-templates-listing.table-header.title' + | translate: { length: (screenStateService.displayedLength$ | async) } + }} - +
-
+
- + - +
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 599bf6e44..eacd843d3 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 @@ -24,23 +24,24 @@
- {{ 'file-attributes-listing.table-header.title' | translate: { length: (displayedEntities$ | async)?.length } }} + {{ + 'file-attributes-listing.table-header.title' | translate: { length: (screenStateService.displayedLength$ | async) } + }} - + >
-
+
- + - + -
+
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 5c84eaab6..e77771430 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 @@ -9,18 +9,18 @@
- {{ 'trash.table-header.title' | translate: { length: (displayedEntities$ | async)?.length } }} + {{ 'trash.table-header.title' | translate: { length: (screenStateService.displayedLength$ | async) } }}
-
+
- + - + -
+
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 d1b4aca33..942ed9395 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 @@ -37,18 +37,18 @@
- {{ 'user-listing.table-header.title' | translate: { length: (displayedEntities$ | async)?.length } }} + {{ 'user-listing.table-header.title' | translate: { length: (screenStateService.displayedLength$ | async) } }}
- + 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 9d2b61546..82508afd6 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 @@ -10,7 +10,7 @@
- {{ 'dossier-listing.table-header.title' | translate: { length: (displayedEntities$ | async)?.length || 0 } }} + {{ 'dossier-listing.table-header.title' | translate: { length: (screenStateService.displayedLength$ | async) } }} @@ -35,17 +35,17 @@ - +
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 e7d1c42b5..1cfa823c3 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 @@ -5,17 +5,17 @@ [showCloseButton]="true" >
- {{ 'dossier-overview.table-header.title' | translate: { length: (displayedEntities$ | async)?.length || 0 } }} + {{ 'dossier-overview.table-header.title' | translate: { length: (screenStateService.displayedLength$ | async) || 0 } }} @@ -54,7 +54,7 @@
-
+
@@ -106,20 +106,17 @@ - +
extends AutoUnsubscribeComponent implements OnDestroy { @ViewChild(CdkVirtualScrollViewport) readonly scrollViewport: CdkVirtualScrollViewport; + readonly permissionsService: PermissionsService; readonly filterService: FilterService; readonly sortingService: SortingService; readonly searchService: SearchService; @@ -19,9 +22,10 @@ export abstract class BaseListingComponent extends AutoUnsubscribeComponent i protected constructor(protected readonly _injector: Injector) { super(); - this.filterService = this._injector.get>(FilterService); - this.sortingService = this._injector.get(SortingService); - this.searchService = this._injector.get>(SearchService); + this.permissionsService = this._injector.get(PermissionsService); + this.filterService = this._injector.get(FilterService); + this.sortingService = this._injector.get(SortingService); + this.searchService = this._injector.get(SearchService); this.screenStateService = this._injector.get>(ScreenStateService); } @@ -29,44 +33,30 @@ export abstract class BaseListingComponent extends AutoUnsubscribeComponent i super.ngOnDestroy(); } - get selectedEntities$(): Observable { - return this.screenStateService.selectedEntities$; - } - get displayedEntities$(): Observable { return this.screenStateService.displayedEntities$; } - get allEntities$(): Observable { - return this.screenStateService.allEntities$; - } - - get displayedEntities(): T[] { - return this.screenStateService.displayedEntities; + get sortedDisplayedEntities$(): Observable { + return this.screenStateService.displayedEntities$.pipe(map(entities => this.sortingService.defaultSort(entities))); } get allEntities(): T[] { return this.screenStateService.allEntities; } - get areAllEntitiesSelected() { - return this.screenStateService.areAllEntitiesSelected; - } - - get areSomeEntitiesSelected$(): Observable { - return this.screenStateService.areSomeEntitiesSelected$; - } - get sortingOption(): SortingOption { return this.sortingService.getSortingOption(); } - get noMatch(): boolean { - return this.allEntities.length && this.displayedEntities?.length === 0; + get noMatch$(): Observable { + return combineLatest([this.screenStateService.allEntitiesLength$, this.screenStateService.displayedLength$]).pipe( + map(res => res[0] && !res[1]) + ); } - get noData(): boolean { - return this.allEntities.length === 0; + canBulkDelete$(hasPermission = true) { + return this.screenStateService.areSomeEntitiesSelected$.pipe(map(res => res && hasPermission)); } toggleSort($event) { @@ -74,12 +64,12 @@ export abstract class BaseListingComponent extends AutoUnsubscribeComponent i } toggleSelectAll() { - return this.screenStateService.toggleSelectAll(); + return this.screenStateService.selectEntities(); } toggleEntitySelected(event: MouseEvent, entity: T) { event.stopPropagation(); - return this.screenStateService.toggleEntitySelected(entity); + return this.screenStateService.selectEntities([entity]); } isSelected(entity: T): boolean { diff --git a/apps/red-ui/src/app/modules/shared/components/empty-state/empty-state.component.html b/apps/red-ui/src/app/modules/shared/components/empty-state/empty-state.component.html index 50223bb5c..c8f5095d2 100644 --- a/apps/red-ui/src/app/modules/shared/components/empty-state/empty-state.component.html +++ b/apps/red-ui/src/app/modules/shared/components/empty-state/empty-state.component.html @@ -17,6 +17,5 @@ [icon]="buttonIcon" [text]="screen + '.no-data.action'" type="primary" - > - + >
diff --git a/apps/red-ui/src/app/modules/shared/components/empty-state/empty-state.component.ts b/apps/red-ui/src/app/modules/shared/components/empty-state/empty-state.component.ts index b42f0e62e..6b3b53f12 100644 --- a/apps/red-ui/src/app/modules/shared/components/empty-state/empty-state.component.ts +++ b/apps/red-ui/src/app/modules/shared/components/empty-state/empty-state.component.ts @@ -1,9 +1,10 @@ -import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; @Component({ selector: 'redaction-empty-state', templateUrl: './empty-state.component.html', - styleUrls: ['./empty-state.component.scss'] + styleUrls: ['./empty-state.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush }) export class EmptyStateComponent implements OnInit { @Input() screen: string; @@ -16,8 +17,6 @@ export class EmptyStateComponent implements OnInit { @Input() type: 'no-data' | 'no-match' = 'no-data'; @Output() action = new EventEmitter(); - constructor() {} - ngOnInit(): void { this.showButton = this.showButton && this.action.observers.length > 0; } 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 48a3aee2f..bb2ade371 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,23 +1,11 @@ import { Pipe, PipeTransform } from '@angular/core'; -import { orderBy } from 'lodash'; -import { SortingOrders } from '@services/sorting.service'; +import { SortingService } from '@services/sorting.service'; @Pipe({ name: 'sortBy' }) export class SortByPipe implements PipeTransform { + constructor(private readonly _sortingService: SortingService) {} + transform(value: T[], order = '', column: string = ''): T[] { - if (!value || order === '' || !order) { - return value; - } // no array - if (!column || column === '') { - if (order === SortingOrders.ASC) { - return value.sort(); - } else { - return value.sort().reverse(); - } - } // sort 1d array - if (value.length <= 1) { - return value; - } // array with only one item - return orderBy(value, [column], [order]); + return this._sortingService.sort(value, order, column); } } 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 e8305e8d0..3a34ca3fe 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 @@ -1,5 +1,5 @@ import { Injectable } from '@angular/core'; -import { BehaviorSubject, Observable } from 'rxjs'; +import { BehaviorSubject, combineLatest, Observable } from 'rxjs'; import { distinctUntilChanged, map } from 'rxjs/operators'; @Injectable() @@ -48,29 +48,58 @@ export class ScreenStateService { this.displayedEntities$.next(newEntities); } - get areAllEntitiesSelected(): boolean { - return this.displayedEntities.length !== 0 && this.selectedEntities.length === this.displayedEntities.length; + get noData$(): Observable { + return this.allEntitiesLength$.pipe(map(length => length === 0)); } + /** + * Returns the length of all entities + */ + get allEntitiesLength$(): Observable { + return this.allEntities$.pipe(map(all => all?.length ?? 0)); + } + + /** + * Returns the length of the currently displayed entities + */ + get displayedLength$(): Observable { + return this.displayedEntities$.pipe(map(all => all?.length ?? 0)); + } + + /** + * Returns the length of the selected entities + */ + get selectedLength$(): Observable { + return this.selectedEntities$.pipe(map(all => all?.length ?? 0)); + } + + get areAllEntitiesSelected$(): Observable { + return combineLatest([this.displayedLength$, this.selectedLength$]).pipe(map(res => res[0] && res[0] === res[1])); + } + + /** + * Indicates that some entities are selected. If all are selected this returns true + */ get areSomeEntitiesSelected$(): Observable { - return this.selectedEntities$.pipe(map(all => all.length > 0)); + return this.selectedLength$.pipe(map(value => value > 0)); + } + + /** + * Indicates that some entities are selected, but not all + */ + get notAllEntitiesSelected$(): Observable { + return combineLatest([this.areAllEntitiesSelected$, this.areSomeEntitiesSelected$]).pipe(map(res => !res[0] && res[1])); } isSelected(entity: T): boolean { return this.selectedEntities.indexOf(entity) !== -1; } - toggleEntitySelected(entity: T): void { - const currentEntityIdx = this.selectedEntities.indexOf(entity); - if (currentEntityIdx === -1) { - return this.setSelectedEntities([...this.selectedEntities, entity]); + selectEntities(entities?: T[]): void { + if (entities !== undefined && entities !== null && entities.length > 0) { + return entities.forEach(entity => this._selectOne(entity)); } - this.setSelectedEntities(this.selectedEntities.filter((el, idx) => idx !== currentEntityIdx)); - } - - toggleSelectAll(): void { - if (this.areAllEntitiesSelected) return this.setSelectedEntities([]); - this.setSelectedEntities(this.displayedEntities); + return this._selectAll(); } updateSelection(): void { @@ -84,4 +113,21 @@ export class ScreenStateService { console.log('Filtered', this.filteredEntities); console.log('Selected', this.selectedEntities); } + + private _selectOne(entity: T): void { + const currentEntityIdx = this.selectedEntities.indexOf(entity); + if (currentEntityIdx === -1) { + return this.setSelectedEntities([...this.selectedEntities, entity]); + } + this.setSelectedEntities(this.selectedEntities.filter((el, idx) => idx !== currentEntityIdx)); + } + + private _selectAll(): void { + if (this._allEntitiesSelected) return this.setSelectedEntities([]); + this.setSelectedEntities(this.displayedEntities); + } + + private get _allEntitiesSelected() { + return this.displayedEntities.length !== 0 && this.displayedEntities.length === this.selectedEntities.length; + } } diff --git a/apps/red-ui/src/app/services/sorting.service.ts b/apps/red-ui/src/app/services/sorting.service.ts index b2a07b5db..b43f2ba56 100644 --- a/apps/red-ui/src/app/services/sorting.service.ts +++ b/apps/red-ui/src/app/services/sorting.service.ts @@ -1,8 +1,9 @@ import { Injectable } from '@angular/core'; +import { orderBy } from 'lodash'; export type SortingOrder = 'asc' | 'desc'; -export enum SortingOrders { +export const enum SortingOrders { ASC = 'asc', DESC = 'desc' } @@ -21,7 +22,7 @@ export type ScreenName = | 'file-attributes-listing' | 'dossier-attributes-listing'; -export enum ScreenNames { +export const enum ScreenNames { DOSSIER_LISTING = 'dossier-listing', DOSSIER_OVERVIEW = 'dossier-overview', DICTIONARY_LISTING = 'dictionary-listing', @@ -48,6 +49,27 @@ export class SortingService { this._currentScreenName = value; } + sort(values: T[], order = '', column: string = ''): T[] { + if (!values || order === '' || !order) { + return values; + } // no array + if (!column || column === '') { + if (order === SortingOrders.ASC) { + return values.sort(); + } else { + return values.sort().reverse(); + } + } // sort 1d array + if (values.length <= 1) { + return values; + } // array with only one item + return orderBy(values, [column], [order]); + } + + defaultSort(values: T[]) { + return this.sort(values, this.getSortingOption().order, this.getSortingOption().column); + } + toggleSort(column: string) { if (this._options[this._currentScreenName].column === column) { this._currentOrder = this._currentOrder === SortingOrders.ASC ? SortingOrders.DESC : SortingOrders.ASC; @@ -56,7 +78,7 @@ export class SortingService { } } - getSortingOption() { + getSortingOption(): SortingOption { return this._options[this._currentScreenName]; } diff --git a/apps/red-ui/src/app/services/toaster.service.ts b/apps/red-ui/src/app/services/toaster.service.ts index c24fe23d0..f389df656 100644 --- a/apps/red-ui/src/app/services/toaster.service.ts +++ b/apps/red-ui/src/app/services/toaster.service.ts @@ -7,7 +7,7 @@ import { HttpErrorResponse } from '@angular/common/http'; import { ErrorMessageService } from '@services/error-message.service'; import { filter } from 'rxjs/operators'; -export enum NotificationType { +const enum NotificationType { SUCCESS = 'SUCCESS', WARNING = 'WARNING', INFO = 'INFO' @@ -67,7 +67,7 @@ export class Toaster { message: string, notificationType = NotificationType.INFO, options?: Partial - ): ActiveToast { + ): ActiveToast { message = this._translateService.instant(message, options?.params); switch (notificationType) { diff --git a/apps/red-ui/src/app/services/user.service.ts b/apps/red-ui/src/app/services/user.service.ts index bfc12b023..306dcac07 100644 --- a/apps/red-ui/src/app/services/user.service.ts +++ b/apps/red-ui/src/app/services/user.service.ts @@ -16,9 +16,8 @@ export interface ProfileModel { export class UserWrapper { name: string; - constructor(private _currentUser: KeycloakProfile, public roles: string[], public id: string) { - this.name = - this.firstName && this.lastName ? `${this.firstName} ${this.lastName}` : this.username; + constructor(private readonly _currentUser: KeycloakProfile, public roles: string[], public id: string) { + this.name = this.firstName && this.lastName ? `${this.firstName} ${this.lastName}` : this.username; } get username() { @@ -91,9 +90,7 @@ export class UserService { } get eligibleUsers(): User[] { - return this._allRedUsers.filter( - u => u.roles.indexOf('RED_USER') >= 0 || u.roles.indexOf('RED_MANAGER') >= 0 - ); + return this._allRedUsers.filter(u => u.roles.indexOf('RED_USER') >= 0 || u.roles.indexOf('RED_MANAGER') >= 0); } get user() { @@ -153,10 +150,8 @@ export class UserService { return this.getName(this.getUserById(userId)); } - getName(user?: User) { - return user?.firstName && user?.lastName - ? `${user.firstName} ${user.lastName}` - : user?.username; + getName({ firstName, lastName, username }: User = {}) { + return firstName && lastName ? `${firstName} ${lastName}` : username; } isManager(user: User | UserWrapper = this.user): boolean { diff --git a/apps/red-ui/src/assets/i18n/en.json b/apps/red-ui/src/assets/i18n/en.json index 0a6b42e2f..575881c72 100644 --- a/apps/red-ui/src/assets/i18n/en.json +++ b/apps/red-ui/src/assets/i18n/en.json @@ -1330,6 +1330,11 @@ "children": { "admin": "Settings", "downloads": "My Downloads", + "language": { + "de": "German", + "en": "English", + "label": "Language" + }, "my-profile": "My Profile", "trash": "Trash", "logout": "Logout" From 27de88ac57899e39b241125239acea033d469912 Mon Sep 17 00:00:00 2001 From: Dan Percic Date: Fri, 16 Jul 2021 02:18:29 +0300 Subject: [PATCH 4/6] simplify sorting, add common config to base listing --- .../active-fields-listing.component.html | 2 +- .../active-fields-listing.component.ts | 1 + ...ttributes-csv-import-dialog.component.html | 7 +- ...-attributes-csv-import-dialog.component.ts | 6 +- .../screens/audit/audit-screen.component.html | 73 ++++--------------- .../screens/audit/audit-screen.component.ts | 29 +++----- .../default-colors-screen.component.html | 9 +-- .../default-colors-screen.component.ts | 23 +++--- .../dictionary-listing-screen.component.html | 12 +-- .../dictionary-listing-screen.component.ts | 8 +- .../digital-signature-screen.component.html | 20 +---- .../digital-signature-screen.component.ts | 7 +- ...r-attributes-listing-screen.component.html | 9 +-- ...ier-attributes-listing-screen.component.ts | 6 +- ...er-templates-listing-screen.component.html | 38 +++------- ...sier-templates-listing-screen.component.ts | 6 +- ...e-attributes-listing-screen.component.html | 8 +- ...ile-attributes-listing-screen.component.ts | 10 +-- .../license-information-screen.component.html | 57 +++------------ .../license-information-screen.component.ts | 31 +++++--- .../screens/trash/trash-screen.component.html | 8 +- .../screens/trash/trash-screen.component.ts | 8 +- .../user-listing-screen.component.html | 2 +- .../user-listing-screen.component.ts | 6 +- ...ssier-overview-bulk-actions.component.html | 20 ++--- ...dossier-overview-bulk-actions.component.ts | 7 +- .../dossier-details.component.ts | 2 +- .../dossier-listing-actions.component.ts | 10 +-- .../dossier-listing-screen.component.html | 6 +- .../dossier-listing-screen.component.ts | 10 +-- .../dossier-overview-screen.component.html | 12 +-- .../dossier-overview-screen.component.ts | 21 ++---- .../shared/base/base-listing.component.ts | 37 +++++++--- .../page-header/page-header.component.html | 31 ++++---- .../page-header/page-header.component.ts | 32 +++++--- .../table-col-name.component.html | 17 ++--- .../table-col-name.component.ts | 7 +- .../modules/shared/services/filter.service.ts | 2 +- .../shared/services/screen-state.service.ts | 8 +- .../modules/shared/services/search.service.ts | 16 ++-- .../src/app/services/sorting.service.ts | 52 +++---------- .../src/app/utils/sorters/status-sorter.ts | 12 ++- 42 files changed, 264 insertions(+), 424 deletions(-) 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 index 201646c69..e32368ef4 100644 --- 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 @@ -88,7 +88,7 @@
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 e1301401a..cdaceeb4d 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 @@ -20,6 +20,7 @@ export class ActiveFieldsListingComponent extends BaseListingComponent im @Output() toggleFieldActive = new EventEmitter(); readonly typeOptions = [FileAttributeConfig.TypeEnum.TEXT, FileAttributeConfig.TypeEnum.NUMBER, FileAttributeConfig.TypeEnum.DATE]; + protected readonly _primaryKey = 'id'; constructor(protected readonly _injector: Injector) { super(_injector); 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 1b5300bfd..c3d2ccc74 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 @@ -101,7 +101,7 @@ (click)="toggleFieldActive(field)" (mouseenter)="setHoveredColumn(field.csvColumn)" (mouseleave)="setHoveredColumn()" - *ngFor="let field of displayedEntities$ | async" + *ngFor="let field of displayedEntities$ | async; trackBy: trackByPrimaryKey" class="csv-header-pill-wrapper" >
@@ -109,7 +109,10 @@ {{ field.csvColumn }}
-
{{ getEntries(field.csvColumn) }} entries
+
+ {{ getEntries(field.csvColumn) }} + entries +
Sample: {{ getSample(field.csvColumn) }}
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 d933b71e8..4de397f09 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 @@ -31,6 +31,8 @@ export interface Field { providers: [FilterService, SearchService, ScreenStateService, SortingService] }) export class FileAttributesCsvImportDialogComponent extends BaseListingComponent { + protected readonly _primaryKey = 'id'; + csvFile: File; dossierTemplateId: string; parseResult: { data: any[]; errors: any[]; meta: any; fields: Field[] }; @@ -50,10 +52,10 @@ export class FileAttributesCsvImportDialogComponent extends BaseListingComponent private readonly _translateService: TranslateService, private readonly _toaster: Toaster, private readonly _formBuilder: FormBuilder, - public dialogRef: MatDialogRef, + readonly dialogRef: MatDialogRef, protected readonly _injector: Injector, @Inject(MAT_DIALOG_DATA) - public data: { + readonly data: { csv: File; dossierTemplateId: string; existingConfiguration: FileAttributesConfig; diff --git a/apps/red-ui/src/app/modules/admin/screens/audit/audit-screen.component.html b/apps/red-ui/src/app/modules/admin/screens/audit/audit-screen.component.html index 9dafe7911..34b6c552b 100644 --- a/apps/red-ui/src/app/modules/admin/screens/audit/audit-screen.component.html +++ b/apps/red-ui/src/app/modules/admin/screens/audit/audit-screen.component.html @@ -22,10 +22,7 @@
- {{ - 'audit-screen.table-header.title' - | translate: { length: logs?.totalHits || 0 } - }} + {{ 'audit-screen.table-header.title' | translate: { length: logs?.totalHits || 0 } }}
- + {{ 'audit-screen.categories.' + category | translate }} @@ -56,10 +50,7 @@ [withName]="true" size="small" > -
+
-
+
·
- + - +
@@ -95,16 +76,9 @@
- + - +
@@ -113,31 +87,18 @@
- - + + - +
- +
@@ -148,11 +109,7 @@ {{ log.recordDate | date: 'd MMM. yyyy, hh:mm a' }}
- +
@@ -162,7 +119,3 @@
- - diff --git a/apps/red-ui/src/app/modules/admin/screens/audit/audit-screen.component.ts b/apps/red-ui/src/app/modules/admin/screens/audit/audit-screen.component.ts index 752b959ac..645c50334 100644 --- a/apps/red-ui/src/app/modules/admin/screens/audit/audit-screen.component.ts +++ b/apps/red-ui/src/app/modules/admin/screens/audit/audit-screen.component.ts @@ -1,10 +1,11 @@ -import { Component } from '@angular/core'; +import { Component, OnDestroy } from '@angular/core'; import { PermissionsService } from '@services/permissions.service'; import { FormBuilder, FormGroup } from '@angular/forms'; import { AuditControllerService, AuditResponse, AuditSearchRequest } from '@redaction/red-ui-http'; -import { TranslateService } from '@ngx-translate/core'; import { Moment } from 'moment'; import { applyIntervalConstraints } from '@utils/date-inputs-utils'; +import { LoadingService } from '../../../../services/loading.service'; +import { AutoUnsubscribeComponent } from '../../../shared/base/auto-unsubscribe.component'; const PAGE_SIZE = 50; @@ -13,16 +14,14 @@ const PAGE_SIZE = 50; templateUrl: './audit-screen.component.html', styleUrls: ['./audit-screen.component.scss'] }) -export class AuditScreenComponent { +export class AuditScreenComponent extends AutoUnsubscribeComponent implements OnDestroy { readonly ALL_CATEGORIES = 'all-categories'; readonly ALL_USERS = 'audit-screen.all-users'; filterForm: FormGroup; - viewReady = false; categories: string[] = []; userIds: Set; logs: AuditResponse; - currentPage = 1; private _previousFrom: Moment; private _previousTo: Moment; @@ -31,8 +30,9 @@ export class AuditScreenComponent { readonly permissionsService: PermissionsService, private readonly _formBuilder: FormBuilder, private readonly _auditControllerService: AuditControllerService, - private readonly _translateService: TranslateService + private readonly _loadingService: LoadingService ) { + super(); this.filterForm = this._formBuilder.group({ category: [this.ALL_CATEGORIES], userId: [this.ALL_USERS], @@ -40,7 +40,7 @@ export class AuditScreenComponent { to: [] }); - this.filterForm.valueChanges.subscribe(value => { + this.addSubscription = this.filterForm.valueChanges.subscribe(value => { if (!this._updateDateFilters(value)) { this._fetchData(); } @@ -61,16 +61,7 @@ export class AuditScreenComponent { } private _updateDateFilters(value): boolean { - if ( - applyIntervalConstraints( - value, - this._previousFrom, - this._previousTo, - this.filterForm, - 'from', - 'to' - ) - ) { + if (applyIntervalConstraints(value, this._previousFrom, this._previousTo, this.filterForm, 'from', 'to')) { return true; } @@ -80,7 +71,7 @@ export class AuditScreenComponent { } private _fetchData(page?: number) { - this.viewReady = false; + this._loadingService.start(); const promises = []; const category = this.filterForm.get('category').value; const userId = this.filterForm.get('userId').value; @@ -110,7 +101,7 @@ export class AuditScreenComponent { for (const id of this.logs.data.map(log => log.userId).filter(uid => !!uid)) { this.userIds.add(id); } - this.viewReady = true; + this._loadingService.stop(); }); } } 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 0269cc400..0aa435152 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 @@ -22,14 +22,12 @@
- {{ 'default-colors-screen.table-header.title' | translate: { length: screenStateService.allEntitiesLength$ } }} + {{ 'default-colors-screen.table-header.title' | translate: { length: screenStateService.allEntitiesLength$ | async } }}
-
+
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 8b21ef348..03a7f0ec9 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,4 +1,4 @@ -import { Component, Injector, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Injector, OnInit } from '@angular/core'; import { AppStateService } from '@state/app-state.service'; import { Colors, DictionaryControllerService } from '@redaction/red-ui-http'; import { ActivatedRoute } from '@angular/router'; @@ -8,12 +8,13 @@ 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 { ScreenNames, SortingService } from '@services/sorting.service'; import { BaseListingComponent } from '@shared/base/base-listing.component'; +import { SortingOrders, SortingService } from '../../../../services/sorting.service'; @Component({ templateUrl: './default-colors-screen.component.html', styleUrls: ['./default-colors-screen.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, providers: [FilterService, SearchService, ScreenStateService, SortingService] }) export class DefaultColorsScreenComponent @@ -24,6 +25,7 @@ export class DefaultColorsScreenComponent implements OnInit { private _colorsObj: Colors; + protected readonly _primaryKey = 'key'; constructor( private readonly _appStateService: AppStateService, @@ -35,7 +37,6 @@ export class DefaultColorsScreenComponent protected readonly _injector: Injector ) { super(_injector); - this.sortingService.setScreenName(ScreenNames.DEFAULT_COLORS); _appStateService.activateDossierTemplate(_activatedRoute.snapshot.params.dossierTemplateId); } @@ -52,9 +53,7 @@ export class DefaultColorsScreenComponent colorKey: color.key, dossierTemplateId: this._appStateService.activeDossierTemplateId }, - async () => { - await this._loadColors(); - } + async () => await this._loadColors() ); } @@ -62,12 +61,12 @@ export class DefaultColorsScreenComponent this._loadingService.start(); const data = await this._dictionaryControllerService.getColors(this._appStateService.activeDossierTemplateId).toPromise(); this._colorsObj = data; - this.screenStateService.setEntities( - Object.keys(data).map(key => ({ - key, - value: data[key] - })) - ); + const entities = Object.keys(data).map(key => ({ + key, + value: data[key] + })); + this.screenStateService.setEntities(entities); + this.screenStateService.setDisplayedEntities(entities); this._loadingService.stop(); } } 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 bce3dd9d8..86d344cda 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 @@ -35,7 +35,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 b3bbee0e3..e9eaf4359 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,4 +1,4 @@ -import { Component, Injector, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Injector, OnInit } from '@angular/core'; import { DoughnutChartConfig } from '@shared/components/simple-doughnut-chart/simple-doughnut-chart.component'; import { DictionaryControllerService } from '@redaction/red-ui-http'; import { AppStateService } from '@state/app-state.service'; @@ -12,7 +12,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 { ScreenNames, SortingService } from '@services/sorting.service'; +import { SortingService } from '../../../../services/sorting.service'; import { BaseListingComponent } from '@shared/base/base-listing.component'; import { AdminDialogService } from '../../services/admin-dialog.service'; @@ -31,6 +31,8 @@ const toChartConfig = (dict: TypeValueWrapper): DoughnutChartConfig => ({ export class DictionaryListingScreenComponent extends BaseListingComponent implements OnInit { chartData: DoughnutChartConfig[] = []; + protected readonly _primaryKey = 'label'; + constructor( private readonly _dialogService: AdminDialogService, private readonly _dictionaryControllerService: DictionaryControllerService, @@ -43,8 +45,6 @@ export class DictionaryListingScreenComponent extends BaseListingComponent
- +
@@ -111,5 +101,3 @@
- - diff --git a/apps/red-ui/src/app/modules/admin/screens/digital-signature/digital-signature-screen.component.ts b/apps/red-ui/src/app/modules/admin/screens/digital-signature/digital-signature-screen.component.ts index a5ac0a556..0a2777a7a 100644 --- a/apps/red-ui/src/app/modules/admin/screens/digital-signature/digital-signature-screen.component.ts +++ b/apps/red-ui/src/app/modules/admin/screens/digital-signature/digital-signature-screen.component.ts @@ -5,6 +5,7 @@ import { Toaster } from '../../../../services/toaster.service'; import { PermissionsService } from '@services/permissions.service'; import { lastIndexOfEnd } from '@utils/functions'; import { AutoUnsubscribeComponent } from '../../../shared/base/auto-unsubscribe.component'; +import { LoadingService } from '../../../../services/loading.service'; @Component({ selector: 'redaction-digital-signature-screen', @@ -15,13 +16,13 @@ export class DigitalSignatureScreenComponent extends AutoUnsubscribeComponent im digitalSignature: DigitalSignature; digitalSignatureForm: FormGroup; - viewReady = false; digitalSignatureExists = false; constructor( private readonly _digitalSignatureControllerService: DigitalSignatureControllerService, private readonly _toaster: Toaster, private readonly _formBuilder: FormBuilder, + private readonly _loadingService: LoadingService, readonly permissionsService: PermissionsService ) { super(); @@ -82,7 +83,7 @@ export class DigitalSignatureScreenComponent extends AutoUnsubscribeComponent im } loadDigitalSignatureAndInitializeForm() { - this.viewReady = false; + this._loadingService.start(); this.addSubscription = this._digitalSignatureControllerService .getDigitalSignature() .subscribe( @@ -97,7 +98,7 @@ export class DigitalSignatureScreenComponent extends AutoUnsubscribeComponent im ) .add(() => { this._initForm(); - this.viewReady = true; + this._loadingService.stop(); }); } diff --git a/apps/red-ui/src/app/modules/admin/screens/dossier-attributes-listing/dossier-attributes-listing-screen.component.html b/apps/red-ui/src/app/modules/admin/screens/dossier-attributes-listing/dossier-attributes-listing-screen.component.html index 5f7c5fe30..c81b649de 100644 --- a/apps/red-ui/src/app/modules/admin/screens/dossier-attributes-listing/dossier-attributes-listing-screen.component.html +++ b/apps/red-ui/src/app/modules/admin/screens/dossier-attributes-listing/dossier-attributes-listing-screen.component.html @@ -65,16 +65,12 @@
-
+
diff --git a/apps/red-ui/src/app/modules/admin/screens/dossier-attributes-listing/dossier-attributes-listing-screen.component.ts b/apps/red-ui/src/app/modules/admin/screens/dossier-attributes-listing/dossier-attributes-listing-screen.component.ts index 5fc724ce7..6f441ae13 100644 --- a/apps/red-ui/src/app/modules/admin/screens/dossier-attributes-listing/dossier-attributes-listing-screen.component.ts +++ b/apps/red-ui/src/app/modules/admin/screens/dossier-attributes-listing/dossier-attributes-listing-screen.component.ts @@ -5,7 +5,7 @@ import { AppStateService } from '@state/app-state.service'; import { ActivatedRoute } from '@angular/router'; import { AdminDialogService } from '../../services/admin-dialog.service'; import { LoadingService } from '@services/loading.service'; -import { ScreenNames, SortingService } from '@services/sorting.service'; +import { SortingService } from '../../../../services/sorting.service'; import { FilterService } from '@shared/services/filter.service'; import { SearchService } from '@shared/services/search.service'; import { ScreenStateService } from '@shared/services/screen-state.service'; @@ -18,6 +18,8 @@ import { DossierAttributesService } from '@shared/services/controller-wrappers/d providers: [FilterService, SearchService, ScreenStateService, SortingService] }) export class DossierAttributesListingScreenComponent extends BaseListingComponent implements OnInit { + protected readonly _primaryKey = 'label'; + constructor( protected readonly _injector: Injector, private readonly _appStateService: AppStateService, @@ -28,8 +30,6 @@ export class DossierAttributesListingScreenComponent extends BaseListingComponen readonly permissionsService: PermissionsService ) { super(_injector); - this.searchService.setSearchKey('label'); - this.sortingService.setScreenName(ScreenNames.DOSSIER_ATTRIBUTES_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.html b/apps/red-ui/src/app/modules/admin/screens/dossier-template-listing/dossier-templates-listing-screen.component.html index 5c63de968..7a4d7b13a 100644 --- a/apps/red-ui/src/app/modules/admin/screens/dossier-template-listing/dossier-templates-listing-screen.component.html +++ b/apps/red-ui/src/app/modules/admin/screens/dossier-template-listing/dossier-templates-listing-screen.component.html @@ -4,17 +4,10 @@
- +
@@ -34,15 +27,13 @@ }} - - - +
+
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 7a3add35e..f08902968 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 @@ -9,8 +9,8 @@ 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 { ScreenNames, SortingService } from '@services/sorting.service'; import { BaseListingComponent } from '@shared/base/base-listing.component'; +import { SortingService } from '../../../../services/sorting.service'; @Component({ templateUrl: './dossier-templates-listing-screen.component.html', @@ -19,6 +19,8 @@ import { BaseListingComponent } from '@shared/base/base-listing.component'; providers: [FilterService, SearchService, ScreenStateService, SortingService] }) export class DossierTemplatesListingScreenComponent extends BaseListingComponent implements OnInit { + protected _primaryKey = 'name'; + constructor( private readonly _dialogService: AdminDialogService, private readonly _appStateService: AppStateService, @@ -29,8 +31,6 @@ export class DossierTemplatesListingScreenComponent extends BaseListingComponent readonly userPreferenceService: UserPreferenceService ) { super(_injector); - this.sortingService.setScreenName(ScreenNames.DOSSIER_TEMPLATES_LISTING); - this.searchService.setSearchKey('name'); } ngOnInit(): void { 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 eacd843d3..ed54e5c43 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 @@ -37,7 +37,7 @@
implements OnInit { +export class FileAttributesListingScreenComponent extends BaseListingComponent implements OnInit, OnDestroy { + protected readonly _primaryKey = 'label'; + private _existingConfiguration: FileAttributesConfig; @ViewChild('fileInput') private _fileInput: ElementRef; @@ -31,8 +33,6 @@ export class FileAttributesListingScreenComponent extends BaseListingComponent -
- +
+
@@ -46,10 +31,7 @@
- {{ - 'license-info-screen.copyright-claim-text' - | translate: { currentYear: currentYear } - }} + {{ 'license-info-screen.copyright-claim-text' | translate: { currentYear: currentYear } }}
@@ -64,10 +46,7 @@ -
+
@@ -92,28 +71,18 @@
{{ currentInfo.numberOfAnalyzedPages }}
-
+
- {{ - 'license-info-screen.total-analyzed' - | translate: { date: totalInfo.startDate | date: 'longDate' } - }} + {{ 'license-info-screen.total-analyzed' | translate: { date: totalInfo.startDate | date: 'longDate' } }}
{{ totalInfo.numberOfAnalyzedPages }}
-
- {{ currentInfo.numberOfAnalyzedPages }} ({{ - analysisPercentageOfLicense | number: '1.0-2' - }}%) -
+
{{ currentInfo.numberOfAnalyzedPages }} ({{ analysisPercentageOfLicense | number: '1.0-2' }}%)
@@ -150,7 +119,3 @@
- - diff --git a/apps/red-ui/src/app/modules/admin/screens/license-information/license-information-screen.component.ts b/apps/red-ui/src/app/modules/admin/screens/license-information/license-information-screen.component.ts index 7dbf54d26..398f76664 100644 --- a/apps/red-ui/src/app/modules/admin/screens/license-information/license-information-screen.component.ts +++ b/apps/red-ui/src/app/modules/admin/screens/license-information/license-information-screen.component.ts @@ -4,6 +4,9 @@ import { LicenseReport, LicenseReportControllerService } from '@redaction/red-ui import { AppConfigService } from '@app-config/app-config.service'; import * as moment from 'moment'; import { TranslateService } from '@ngx-translate/core'; +import { LoadingService } from '../../../../services/loading.service'; +import { ButtonConfig } from '../../../shared/components/page-header/models/button-config.model'; +import { IconButtonTypes } from '../../../shared/components/buttons/icon-button/icon-button.component'; @Component({ selector: 'redaction-license-information-screen', @@ -16,7 +19,6 @@ export class LicenseInformationScreenComponent implements OnInit { unlicensedInfo: LicenseReport = {}; totalLicensedNumberOfPages = 0; analysisPercentageOfLicense = 100; - viewReady = false; barChart: any[] = []; lineChartSeries: any[] = []; yAxisLabel = this._translateService.instant('license-info-screen.chart.pages-per-month'); @@ -31,13 +33,23 @@ export class LicenseInformationScreenComponent implements OnInit { group: 'Ordinal', domain: ['#0389ec'] }; + buttonConfigs: ButtonConfig[] = [ + { + label: this._translateService.instant('license-info-screen.email-report'), + action: () => this.sendMail(), + type: IconButtonTypes.PRIMARY + } + ]; constructor( readonly permissionsService: PermissionsService, readonly appConfigService: AppConfigService, private readonly _licenseReportController: LicenseReportControllerService, - private readonly _translateService: TranslateService - ) {} + private readonly _translateService: TranslateService, + private readonly _loadingService: LoadingService + ) { + _loadingService.start(); + } get currentYear(): number { return new Date().getFullYear(); @@ -63,18 +75,15 @@ export class LicenseInformationScreenComponent implements OnInit { const unlicensedConfig = { startDate: endDate.toDate() }; - promises.push( - this._licenseReportController.licenseReport(unlicensedConfig).toPromise() - ); + promises.push(this._licenseReportController.licenseReport(unlicensedConfig).toPromise()); } Promise.all(promises).then(reports => { [this.currentInfo, this.totalInfo, this.unlicensedInfo] = reports; - this.viewReady = true; + this._loadingService.stop(); this.analysisPercentageOfLicense = this.totalLicensedNumberOfPages > 0 - ? (this.currentInfo.numberOfAnalyzedPages / this.totalLicensedNumberOfPages) * - 100 + ? (this.currentInfo.numberOfAnalyzedPages / this.totalLicensedNumberOfPages) * 100 : 100; }); } @@ -92,9 +101,7 @@ export class LicenseInformationScreenComponent implements OnInit { pages: this.totalLicensedNumberOfPages }) ].join('%0D%0A'); - window.location.href = `mailto:${this.appConfigService.getConfig( - 'LICENSE_EMAIL' - )}?subject=${subject}&body=${body}`; + window.location.href = `mailto:${this.appConfigService.getConfig('LICENSE_EMAIL')}?subject=${subject}&body=${body}`; } private async _setMonthlyStats(startDate: moment.Moment, endDate: moment.Moment) { 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 e77771430..9eeff7dcb 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 @@ -39,8 +39,6 @@
-
+
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 b7f469d6c..1061895c5 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 @@ -8,7 +8,7 @@ import * as moment from 'moment'; 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 { SortingService } from '../../../../services/sorting.service'; import { BaseListingComponent } from '@shared/base/base-listing.component'; import { DossiersService } from '../../../dossier/services/dossiers.service'; @@ -21,6 +21,7 @@ import { DossiersService } from '../../../dossier/services/dossiers.service'; export class TrashScreenComponent extends BaseListingComponent implements OnInit { readonly itemSize = 85; private readonly _deleteRetentionHours = this._appConfigService.getConfig(AppConfigKey.DELETE_RETENTION_HOURS); + protected readonly _primaryKey = 'dossierName'; constructor( private readonly _appStateService: AppStateService, @@ -32,7 +33,6 @@ export class TrashScreenComponent extends BaseListingComponent implemen private readonly _appConfigService: AppConfigService ) { super(_injector); - this.sortingService.setScreenName(ScreenNames.DOSSIER_LISTING); } async ngOnInit(): Promise { @@ -52,10 +52,6 @@ export class TrashScreenComponent extends BaseListingComponent implemen return moment(softDeletedTime).add(this._deleteRetentionHours, 'hours').toISOString(); } - trackById(index: number, dossier: Dossier): string { - return dossier.dossierId; - } - bulkDelete(dossierIds = this.screenStateService.selectedEntities.map(d => d.dossierId)) { this._loadingService.loadWhile(this._hardDelete(dossierIds)); } 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 942ed9395..9ba8b390a 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 @@ -76,7 +76,7 @@ -
+
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 e462094ab..ce7543fb2 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 @@ -23,6 +23,8 @@ import { map } from 'rxjs/operators'; providers: [FilterService, SearchService, ScreenStateService, SortingService] }) export class UserListingScreenComponent extends BaseListingComponent implements OnInit { + protected readonly _primaryKey = 'userId'; + collapsedDetails = false; chartData: DoughnutChartConfig[] = []; @ViewChildren(InitialsAvatarComponent) @@ -81,10 +83,6 @@ export class UserListingScreenComponent extends BaseListingComponent imple this.openDeleteUsersDialog(this.screenStateService.allEntities.filter(u => this.isSelected(u))); } - trackById(index: number, user: User) { - return user.userId; - } - private async _loadData() { this.screenStateService.setEntities(await this._userControllerService.getAllUsers().toPromise()); await this.userService.loadAllUsers(); diff --git a/apps/red-ui/src/app/modules/dossier/components/bulk-actions/dossier-overview-bulk-actions.component.html b/apps/red-ui/src/app/modules/dossier/components/bulk-actions/dossier-overview-bulk-actions.component.html index 0a0f8fc96..13e04a4d8 100644 --- a/apps/red-ui/src/app/modules/dossier/components/bulk-actions/dossier-overview-bulk-actions.component.html +++ b/apps/red-ui/src/app/modules/dossier/components/bulk-actions/dossier-overview-bulk-actions.component.html @@ -21,8 +21,7 @@ icon="red:assign-me" tooltip="dossier-overview.assign-me" type="dark-bg" - > - + > - + > - + > - + - + > - + > ) {} 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 f212e5115..4a65ccbaf 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 @@ -72,7 +72,7 @@ export class DossierDetailsComponent implements OnInit { key: key }); } - this.documentsChartData.sort(StatusSorter.byKey); + this.documentsChartData.sort(StatusSorter.byStatus); this.documentsChartData = this.translateChartService.translateStatus(this.documentsChartData); this._changeDetectorRef.detectChanges(); } diff --git a/apps/red-ui/src/app/modules/dossier/components/dossier-listing-actions/dossier-listing-actions.component.ts b/apps/red-ui/src/app/modules/dossier/components/dossier-listing-actions/dossier-listing-actions.component.ts index ae79b6868..e8f6d2469 100644 --- a/apps/red-ui/src/app/modules/dossier/components/dossier-listing-actions/dossier-listing-actions.component.ts +++ b/apps/red-ui/src/app/modules/dossier/components/dossier-listing-actions/dossier-listing-actions.component.ts @@ -2,7 +2,6 @@ import { Component, EventEmitter, Input, Output } from '@angular/core'; import { PermissionsService } from '@services/permissions.service'; import { DossierWrapper } from '@state/model/dossier.wrapper'; import { StatusSorter } from '@utils/sorters/status-sorter'; -import { FileManagementControllerService } from '@redaction/red-ui-http'; import { AppStateService } from '@state/app-state.service'; import { DossiersDialogService } from '../../services/dossiers-dialog.service'; @@ -19,16 +18,13 @@ export class DossierListingActionsComponent { constructor( readonly permissionsService: PermissionsService, readonly appStateService: AppStateService, - private readonly _dialogService: DossiersDialogService, - private readonly _fileManagementControllerService: FileManagementControllerService + private readonly _dialogService: DossiersDialogService ) {} openEditDossierDialog($event: MouseEvent, dossierWrapper: DossierWrapper) { this._dialogService.openDialog('editDossier', $event, { dossierWrapper, - afterSave: () => { - this.actionPerformed.emit(); - } + afterSave: () => this.actionPerformed.emit() }); } @@ -51,7 +47,7 @@ export class DossierListingActionsComponent { }, {}); return Object.keys(obj) - .sort((a, b) => StatusSorter[a] - StatusSorter[b]) + .sort(StatusSorter.byStatus) .map(status => ({ length: obj[status], color: status })); } } 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 82508afd6..48c30411a 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 @@ -18,10 +18,8 @@
@@ -45,7 +43,7 @@
event instanceof NavigationStart && event.url !== '/main/dossiers'; @@ -39,6 +39,8 @@ const isLeavingScreen = event => event instanceof NavigationStart && event.url ! }) export class DossierListingScreenComponent extends BaseListingComponent implements OnInit, OnDestroy, OnAttach, OnDetach { readonly itemSize = 95; + protected readonly _primaryKey = 'dossierName'; + dossiersChartData: DoughnutChartConfig[] = []; documentsChartData: DoughnutChartConfig[] = []; buttonConfigs: ButtonConfig[] = [ @@ -68,8 +70,6 @@ export class DossierListingScreenComponent extends BaseListingComponent
implements OnInit, OnDestroy, OnDetach, OnAttach { - collapsedDetails = false; readonly itemSize = 80; + protected readonly _primaryKey = 'filename'; + collapsedDetails = false; actionConfigs: ActionConfig[]; dossierAttributes: DossierAttributeWithValue[] = []; @ViewChild(DossierDetailsComponent, { static: false }) @@ -73,8 +74,6 @@ export class DossierOverviewScreenComponent protected readonly _injector: Injector ) { super(_injector); - this.sortingService.setScreenName(ScreenNames.DOSSIER_OVERVIEW); - this.searchService.setSearchKey('searchField'); this._loadEntitiesFromState(); } @@ -82,8 +81,8 @@ export class DossierOverviewScreenComponent return this._appStateService.activeDossier; } - get user() { - return this._userService.user; + get userId() { + return this._userService.userId; } get checkedRequiredFilters() { @@ -170,10 +169,6 @@ export class DossierOverviewScreenComponent this._changeDetectorRef.detectChanges(); } - trackByFileId(index: number, item: FileStatusWrapper) { - return item.fileId; - } - @HostListener('drop', ['$event']) onDrop(event: DragEvent) { handleFileDrop(event, this.activeDossier, this._uploadFiles.bind(this)); @@ -272,7 +267,7 @@ export class DossierOverviewScreenComponent slug: 'statusFilters', label: this._translateService.instant('filters.status'), icon: 'red:status', - values: statusFilters.sort(StatusSorter.byKey), + values: statusFilters.sort(StatusSorter.byStatus), checker: keyChecker('status') }); @@ -348,7 +343,7 @@ export class DossierOverviewScreenComponent { key: 'assigned-to-me', label: this._translateService.instant('dossier-overview.quick-filters.assigned-to-me'), - checker: (file: FileStatusWrapper) => file.currentReviewer === this.user.id + checker: (file: FileStatusWrapper) => file.currentReviewer === this.userId }, { key: 'unassigned', @@ -358,7 +353,7 @@ export class DossierOverviewScreenComponent { key: 'assigned-to-others', label: this._translateService.instant('dossier-overview.quick-filters.assigned-to-others'), - checker: (file: FileStatusWrapper) => !!file.currentReviewer && file.currentReviewer !== this.user.id + checker: (file: FileStatusWrapper) => !!file.currentReviewer && file.currentReviewer !== this.userId } ]; } 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 0f2973a1a..5fef97a9b 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,5 +1,5 @@ import { Component, Injector, OnDestroy, ViewChild } from '@angular/core'; -import { SortingOption, SortingService } from '@services/sorting.service'; +import { SortingOrders, SortingService } from '@services/sorting.service'; import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling'; import { FilterService } from '../services/filter.service'; import { SearchService } from '../services/search.service'; @@ -20,13 +20,30 @@ export abstract class BaseListingComponent extends AutoUnsubscribeComponent i readonly searchService: SearchService; readonly screenStateService: ScreenStateService; + /** + * Key used in the *trackBy* function with **ngFor* or **cdkVirtualFor* + * and in the default sorting and as the search field + * @protected + */ + protected abstract _primaryKey: string; + protected constructor(protected readonly _injector: Injector) { super(); + this.trackByPrimaryKey = this.trackByPrimaryKey.bind(this); this.permissionsService = this._injector.get(PermissionsService); this.filterService = this._injector.get(FilterService); this.sortingService = this._injector.get(SortingService); this.searchService = this._injector.get(SearchService); this.screenStateService = this._injector.get>(ScreenStateService); + setTimeout(() => this.setInitialConfig()); + } + + setInitialConfig() { + this.sortingService.setSortingOption({ + column: this._primaryKey, + order: SortingOrders.ASC + }); + this.searchService.setSearchKey(this._primaryKey); } ngOnDestroy(): void { @@ -45,22 +62,16 @@ export abstract class BaseListingComponent extends AutoUnsubscribeComponent i return this.screenStateService.allEntities; } - get sortingOption(): SortingOption { - return this.sortingService.getSortingOption(); - } - get noMatch$(): Observable { return combineLatest([this.screenStateService.allEntitiesLength$, this.screenStateService.displayedLength$]).pipe( - map(res => res[0] && !res[1]) + map(([hasEntities, hasDisplayedEntities]) => hasEntities && !hasDisplayedEntities) ); } canBulkDelete$(hasPermission = true) { - return this.screenStateService.areSomeEntitiesSelected$.pipe(map(res => res && hasPermission)); - } - - toggleSort($event) { - this.sortingService.toggleSort($event); + return this.screenStateService.areSomeEntitiesSelected$.pipe( + map(areSomeEntitiesSelected => areSomeEntitiesSelected && hasPermission) + ); } toggleSelectAll() { @@ -75,4 +86,8 @@ export abstract class BaseListingComponent extends AutoUnsubscribeComponent i isSelected(entity: T): boolean { return this.screenStateService.isSelected(entity); } + + trackByPrimaryKey(index: number, item: T) { + return item[this._primaryKey]; + } } 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 ce63e7dbe..38eb57cda 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 @@ -16,31 +16,26 @@ -
+
- - - +
+ + + -
{ @Input() buttonConfigs: ButtonConfig[]; @Input() searchPlaceholder: string; - constructor( - readonly permissionsService: PermissionsService, - readonly filterService: FilterService, - readonly searchService: SearchService - ) {} + constructor(@Optional() readonly filterService: FilterService, @Optional() readonly searchService: SearchService) {} - get filters$() { - return this.filterService.allFilters$.pipe(map(all => all.filter(f => f.icon))); + get filters$(): Observable { + return this.filterService?.allFilters$.pipe(map(all => all.filter(f => f.icon))); } - resetFilters() { + get showResetFilters$(): Observable { + const filtersLength$ = this.filters$.pipe( + map(f => f.length), + distinctUntilChanged() + ); + return combineLatest([filtersLength$, this.filterService.showResetFilters$, this.searchService.valueChanges$]).pipe( + map(([hasFilters, showResetFilters, searchValue]) => hasFilters && (showResetFilters || !!searchValue)), + distinctUntilChanged() + ); + } + + resetFilters(): void { this.filterService.reset(); this.searchService.reset(); } - trackByLabel(index: number, item) { + trackByLabel(index: number, item: K): string { return item.label; } } diff --git a/apps/red-ui/src/app/modules/shared/components/table-col-name/table-col-name.component.html b/apps/red-ui/src/app/modules/shared/components/table-col-name/table-col-name.component.html index d34610c8a..8cacef41f 100644 --- a/apps/red-ui/src/app/modules/shared/components/table-col-name/table-col-name.component.html +++ b/apps/red-ui/src/app/modules/shared/components/table-col-name/table-col-name.component.html @@ -1,18 +1,13 @@ -
+
- +
- - + +
diff --git a/apps/red-ui/src/app/modules/shared/components/table-col-name/table-col-name.component.ts b/apps/red-ui/src/app/modules/shared/components/table-col-name/table-col-name.component.ts index 34328716e..d90b3d867 100644 --- a/apps/red-ui/src/app/modules/shared/components/table-col-name/table-col-name.component.ts +++ b/apps/red-ui/src/app/modules/shared/components/table-col-name/table-col-name.component.ts @@ -1,5 +1,5 @@ -import { Component, EventEmitter, Input, Output } from '@angular/core'; -import { SortingOption } from '@services/sorting.service'; +import { Component, Input, Optional } from '@angular/core'; +import { SortingService } from '@services/sorting.service'; @Component({ selector: 'redaction-table-col-name', @@ -7,7 +7,6 @@ import { SortingOption } from '@services/sorting.service'; styleUrls: ['./table-col-name.component.scss'] }) export class TableColNameComponent { - @Input() activeSortingOption: SortingOption; @Input() column: string; @Input() label: string; @Input() withSort = false; @@ -16,5 +15,5 @@ export class TableColNameComponent { @Input() rightIcon: string; @Input() rightIconTooltip: string; - @Output() toggleSort = new EventEmitter(); + constructor(@Optional() readonly sortingService: SortingService) {} } 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 b26f019ef..169e26655 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 @@ -24,7 +24,7 @@ export class FilterService { get showResetFilters$() { return this.allFilters$.pipe( map(all => this._toFlatFilters(all)), - filter(f => !!f.find(el => el.checked)), + map(f => !!f.find(el => el.checked)), distinctUntilChanged() ); } 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 3a34ca3fe..80cc99002 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 @@ -74,7 +74,9 @@ export class ScreenStateService { } get areAllEntitiesSelected$(): Observable { - return combineLatest([this.displayedLength$, this.selectedLength$]).pipe(map(res => res[0] && res[0] === res[1])); + return combineLatest([this.displayedLength$, this.selectedLength$]).pipe( + map(([displayedLength, selectedLength]) => displayedLength && displayedLength === selectedLength) + ); } /** @@ -88,7 +90,9 @@ export class ScreenStateService { * Indicates that some entities are selected, but not all */ get notAllEntitiesSelected$(): Observable { - return combineLatest([this.areAllEntitiesSelected$, this.areSomeEntitiesSelected$]).pipe(map(res => !res[0] && res[1])); + return combineLatest([this.areAllEntitiesSelected$, this.areSomeEntitiesSelected$]).pipe( + map(([allEntitiesAreSelected, someEntitiesAreSelected]) => !allEntitiesAreSelected && someEntitiesAreSelected) + ); } isSelected(entity: T): boolean { 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 25a8f19b7..78fbe8a0d 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 @@ -2,18 +2,24 @@ import { Injectable } from '@angular/core'; import { debounce } from '@utils/debounce'; import { ScreenStateService } from '@shared/services/screen-state.service'; import { FormBuilder } from '@angular/forms'; +import { BehaviorSubject } from 'rxjs'; @Injectable() export class SearchService { private _searchValue = ''; private _searchKey: string; + valueChanges$ = new BehaviorSubject(this._searchValue); + readonly searchForm = this._formBuilder.group({ query: [''] }); constructor(private readonly _screenStateService: ScreenStateService, private readonly _formBuilder: FormBuilder) { - this.searchForm.valueChanges.subscribe(() => this.executeSearch()); + this.searchForm.valueChanges.subscribe(() => { + this.valueChanges$.next(this.searchValue.toLowerCase()); + this.executeSearch(); + }); } @debounce(200) @@ -23,7 +29,9 @@ export class SearchService { } executeSearchImmediately(): void { - const displayed = this._screenStateService.filteredEntities || this._screenStateService.allEntities; + const displayed = this._screenStateService.filteredEntities.length + ? this._screenStateService.filteredEntities + : this._screenStateService.allEntities; if (!this._searchKey) { return this._screenStateService.setDisplayedEntities(displayed); @@ -39,10 +47,6 @@ export class SearchService { this._searchKey = value; } - get isSearchNeeded(): boolean { - return !!this._searchKey; - } - get searchValue(): string { return this.searchForm.get('query').value; } diff --git a/apps/red-ui/src/app/services/sorting.service.ts b/apps/red-ui/src/app/services/sorting.service.ts index b43f2ba56..c6f620aeb 100644 --- a/apps/red-ui/src/app/services/sorting.service.ts +++ b/apps/red-ui/src/app/services/sorting.service.ts @@ -13,40 +13,12 @@ export interface SortingOption { column: string; } -export type ScreenName = - | 'dossier-listing' - | 'dossier-overview' - | 'dictionary-listing' - | 'dossier-templates-listing' - | 'default-colors' - | 'file-attributes-listing' - | 'dossier-attributes-listing'; - -export const enum ScreenNames { - DOSSIER_LISTING = 'dossier-listing', - DOSSIER_OVERVIEW = 'dossier-overview', - DICTIONARY_LISTING = 'dictionary-listing', - DOSSIER_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 _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.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' } - }; + private _sortingOption: SortingOption; - setScreenName(value: string) { - this._currentScreenName = value; + setSortingOption(value: SortingOption): void { + this._sortingOption = value; } sort(values: T[], order = '', column: string = ''): T[] { @@ -67,26 +39,26 @@ export class SortingService { } defaultSort(values: T[]) { - return this.sort(values, this.getSortingOption().order, this.getSortingOption().column); + return this.sort(values, this.sortingOption?.order, this.sortingOption?.column); } toggleSort(column: string) { - if (this._options[this._currentScreenName].column === column) { + if (this._sortingOption.column === column) { this._currentOrder = this._currentOrder === SortingOrders.ASC ? SortingOrders.DESC : SortingOrders.ASC; } else { - this._options[this._currentScreenName] = { column, order: SortingOrders.ASC }; + this._sortingOption = { column, order: SortingOrders.ASC }; } } - getSortingOption(): SortingOption { - return this._options[this._currentScreenName]; + get sortingOption(): SortingOption { + return this._sortingOption; } - private get _currentOrder(): string { - return this._options[this._currentScreenName].order; + private get _currentOrder(): SortingOrder { + return this._sortingOption.order; } - private set _currentOrder(value: string) { - this._options[this._currentScreenName].order = value; + private set _currentOrder(value: SortingOrder) { + this._sortingOption.order = value; } } diff --git a/apps/red-ui/src/app/utils/sorters/status-sorter.ts b/apps/red-ui/src/app/utils/sorters/status-sorter.ts index 94ec85387..bb840b6ef 100644 --- a/apps/red-ui/src/app/utils/sorters/status-sorter.ts +++ b/apps/red-ui/src/app/utils/sorters/status-sorter.ts @@ -1,3 +1,13 @@ +type StatusSorterItem = { key?: string } | string; + +const byStatus = (a: StatusSorterItem, b: StatusSorterItem) => { + if (typeof a !== typeof b) return; + + const x = typeof a === 'string' ? a : a.key; + const y = typeof b === 'string' ? b : b.key; + return (StatusSorter[x] = StatusSorter[y]); +}; + export const StatusSorter = { ERROR: 0, UNPROCESSED: 1, @@ -9,5 +19,5 @@ export const StatusSorter = { UNDER_REVIEW: 15, UNDER_APPROVAL: 20, APPROVED: 25, - byKey: (a: { key?: string }, b: { key?: string }) => StatusSorter[a.key] - StatusSorter[b.key] + byStatus: byStatus }; From 02b38780f6fd3336df7cf68b1485a8452928c6e5 Mon Sep 17 00:00:00 2001 From: Dan Percic Date: Fri, 16 Jul 2021 14:43:44 +0300 Subject: [PATCH 5/6] add table header component, remove filteredEntities --- .../active-fields-listing.component.html | 2 +- ...ttributes-csv-import-dialog.component.html | 2 +- ...ier-attributes-listing-screen.component.ts | 2 +- ...sier-templates-listing-screen.component.ts | 2 +- ...ile-attributes-listing-screen.component.ts | 2 +- .../screens/trash/trash-screen.component.ts | 4 +- .../user-listing-screen.component.html | 2 +- .../user-listing-screen.component.ts | 2 +- .../dossier-details.component.html | 2 +- .../dossier-listing-screen.component.html | 26 +----- .../dossier-listing-screen.component.ts | 39 ++++++--- .../dossier-overview-screen.component.ts | 14 ++-- .../shared/base/base-listing.component.ts | 14 ++-- .../model/filter-wrapper.model.ts | 2 +- .../popup-filter/utils/filter-utils.ts | 4 +- .../quick-filters.component.html | 2 +- .../page-header/page-header.component.html | 2 +- .../page-header/page-header.component.ts | 6 +- .../simple-doughnut-chart.component.html | 2 +- .../table-col-name.component.ts | 10 +++ .../table-header/table-header.component.html | 22 +++++ .../table-header/table-header.component.scss | 0 .../table-header/table-header.component.ts | 16 ++++ .../shared/directives/sync-width.directive.ts | 22 ++--- .../sort-pipe => pipes}/sort-by.pipe.ts | 2 +- .../modules/shared/services/filter.service.ts | 66 +++++++-------- .../shared/services/screen-state.service.ts | 81 ++++++++++++------- .../modules/shared/services/search.service.ts | 9 +-- .../src/app/modules/shared/shared.module.ts | 7 +- 29 files changed, 213 insertions(+), 153 deletions(-) create mode 100644 apps/red-ui/src/app/modules/shared/components/table-header/table-header.component.html create mode 100644 apps/red-ui/src/app/modules/shared/components/table-header/table-header.component.scss create mode 100644 apps/red-ui/src/app/modules/shared/components/table-header/table-header.component.ts rename apps/red-ui/src/app/modules/shared/{components/sort-pipe => pipes}/sort-by.pipe.ts (83%) 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 index e32368ef4..c98448df1 100644 --- 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 @@ -88,7 +88,7 @@
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 c3d2ccc74..1b50e47f0 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 @@ -101,7 +101,7 @@ (click)="toggleFieldActive(field)" (mouseenter)="setHoveredColumn(field.csvColumn)" (mouseleave)="setHoveredColumn()" - *ngFor="let field of displayedEntities$ | async; trackBy: trackByPrimaryKey" + *ngFor="let field of sortedDisplayedEntities$ | async; trackBy: trackByPrimaryKey" class="csv-header-pill-wrapper" >
diff --git a/apps/red-ui/src/app/modules/admin/screens/dossier-attributes-listing/dossier-attributes-listing-screen.component.ts b/apps/red-ui/src/app/modules/admin/screens/dossier-attributes-listing/dossier-attributes-listing-screen.component.ts index 6f441ae13..8a2e518f9 100644 --- a/apps/red-ui/src/app/modules/admin/screens/dossier-attributes-listing/dossier-attributes-listing-screen.component.ts +++ b/apps/red-ui/src/app/modules/admin/screens/dossier-attributes-listing/dossier-attributes-listing-screen.component.ts @@ -61,7 +61,7 @@ export class DossierAttributesListingScreenComponent extends BaseListingComponen this._loadingService.start(); const attributes = await this._dossierAttributesService.getConfig(); this.screenStateService.setEntities(attributes); - this.filterService.filterEntities(); + this.filterService.applyFilters(); this._loadingService.stop(); } } 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 f08902968..2db3ceb3a 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 @@ -54,7 +54,7 @@ export class DossierTemplatesListingScreenComponent extends BaseListingComponent this._loadingService.start(); this._appStateService.reset(); this.screenStateService.setEntities(this._appStateService.dossierTemplates); - this.filterService.filterEntities(); + this.filterService.applyFilters(); this._loadDossierTemplateStats(); this._loadingService.stop(); } 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 472e56837..be078f557 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 @@ -100,7 +100,7 @@ export class FileAttributesListingScreenComponent extends BaseListingComponent implemen this._loadingService.start(); await this.loadDossierTemplatesData(); - this.filterService.filterEntities(); + this.filterService.applyFilters(); this._loadingService.stop(); } @@ -74,6 +74,6 @@ export class TrashScreenComponent extends BaseListingComponent implemen const entities = this.screenStateService.allEntities.filter(e => !ids.includes(e.dossierId)); this.screenStateService.setEntities(entities); this.screenStateService.setSelectedEntities([]); - this.filterService.filterEntities(); + this.filterService.applyFilters(); } } 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 9ba8b390a..e122bb1b0 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 @@ -76,7 +76,7 @@ -
+
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 ce7543fb2..37ca2be04 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 @@ -86,7 +86,7 @@ export class UserListingScreenComponent extends BaseListingComponent imple private async _loadData() { this.screenStateService.setEntities(await this._userControllerService.getAllUsers().toPromise()); await this.userService.loadAllUsers(); - this.filterService.filterEntities(); + this.filterService.applyFilters(); this._computeStats(); this._loadingService.stop(); } 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 95b943ae7..9a0b8acae 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 @@ -49,7 +49,7 @@
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 48c30411a..d3d8ca424 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 @@ -8,28 +8,10 @@
-
- - {{ 'dossier-listing.table-header.title' | translate: { length: (screenStateService.displayedLength$ | async) } }} - - - -
- -
- - - - - - - -
-
+ event instanceof NavigationStart && event.url !== '/main/dossiers'; @@ -40,9 +41,6 @@ const isLeavingScreen = event => event instanceof NavigationStart && event.url ! export class DossierListingScreenComponent extends BaseListingComponent implements OnInit, OnDestroy, OnAttach, OnDetach { readonly itemSize = 95; protected readonly _primaryKey = 'dossierName'; - - dossiersChartData: DoughnutChartConfig[] = []; - documentsChartData: DoughnutChartConfig[] = []; buttonConfigs: ButtonConfig[] = [ { label: this._translateService.instant('dossier-listing.add-new'), @@ -52,6 +50,27 @@ export class DossierListingScreenComponent extends BaseListingComponent quickFilters.reduce((acc, f) => acc || (f.checked && f.checker(dw)), false) }); - this.filterService.filterEntities(); + this.filterService.applyFilters(); } private _createQuickFilters() { 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 82e737991..980d64688 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 @@ -86,11 +86,11 @@ export class DossierOverviewScreenComponent } get checkedRequiredFilters() { - return this.filterService.getFilter('quickFilters')?.values.filter(f => f.required && f.checked); + return this.filterService.getFilterGroup('quickFilters')?.values.filter(f => f.required && f.checked); } get checkedNotRequiredFilters() { - return this.filterService.getFilter('quickFilters')?.values.filter(f => !f.required && f.checked); + return this.filterService.getFilterGroup('quickFilters')?.values.filter(f => !f.required && f.checked); } isLastOpenedFile({ fileId }: FileStatusWrapper): boolean { @@ -163,7 +163,7 @@ export class DossierOverviewScreenComponent this._loadEntitiesFromState(); this._computeAllFilters(); - this.filterService.filterEntities(); + this.filterService.applyFilters(); this._dossierDetailsComponent?.calculateChartConfig(); this._changeDetectorRef.detectChanges(); @@ -263,7 +263,7 @@ export class DossierOverviewScreenComponent label: this._translateService.instant(item) })); - this.filterService.addFilter({ + this.filterService.addFilterGroup({ slug: 'statusFilters', label: this._translateService.instant('filters.status'), icon: 'red:status', @@ -286,7 +286,7 @@ export class DossierOverviewScreenComponent label: this._userService.getNameForId(userId) }); }); - this.filterService.addFilter({ + this.filterService.addFilterGroup({ slug: 'peopleFilters', label: this._translateService.instant('filters.assigned-people'), icon: 'red:user', @@ -299,7 +299,7 @@ export class DossierOverviewScreenComponent label: this._translateService.instant('filter.' + item) })); - this.filterService.addFilter({ + this.filterService.addFilterGroup({ slug: 'needsWorkFilters', label: this._translateService.instant('filters.needs-work'), icon: 'red:needs-work', @@ -310,7 +310,7 @@ export class DossierOverviewScreenComponent checkerArgs: this.permissionsService }); - this.filterService.addFilter({ + this.filterService.addFilterGroup({ slug: 'quickFilters', values: this._createQuickFilters(), checker: (file: FileStatusWrapper) => 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 5fef97a9b..ee31643b0 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 @@ -6,7 +6,7 @@ import { SearchService } from '../services/search.service'; import { ScreenStateService } from '../services/screen-state.service'; import { combineLatest, Observable } from 'rxjs'; import { AutoUnsubscribeComponent } from './auto-unsubscribe.component'; -import { map } from 'rxjs/operators'; +import { distinctUntilChanged, map } from 'rxjs/operators'; import { PermissionsService } from '../../../services/permissions.service'; @Component({ template: '' }) @@ -50,10 +50,6 @@ export abstract class BaseListingComponent extends AutoUnsubscribeComponent i super.ngOnDestroy(); } - get displayedEntities$(): Observable { - return this.screenStateService.displayedEntities$; - } - get sortedDisplayedEntities$(): Observable { return this.screenStateService.displayedEntities$.pipe(map(entities => this.sortingService.defaultSort(entities))); } @@ -64,13 +60,15 @@ export abstract class BaseListingComponent extends AutoUnsubscribeComponent i get noMatch$(): Observable { return combineLatest([this.screenStateService.allEntitiesLength$, this.screenStateService.displayedLength$]).pipe( - map(([hasEntities, hasDisplayedEntities]) => hasEntities && !hasDisplayedEntities) + map(([hasEntities, hasDisplayedEntities]) => hasEntities && !hasDisplayedEntities), + distinctUntilChanged() ); } - canBulkDelete$(hasPermission = true) { + canBulkDelete$(hasPermission = true): Observable { return this.screenStateService.areSomeEntitiesSelected$.pipe( - map(areSomeEntitiesSelected => areSomeEntitiesSelected && hasPermission) + map(areSomeEntitiesSelected => areSomeEntitiesSelected && hasPermission), + distinctUntilChanged() ); } 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 1b19edc1d..ccdb3e6a8 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,7 +1,7 @@ import { FilterModel } from './filter.model'; import { TemplateRef } from '@angular/core'; -export interface FilterWrapper { +export interface FilterGroup { slug: string; label?: string; icon?: string; 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 753c65f68..720d31006 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 @@ -2,7 +2,7 @@ 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 { FilterWrapper } from '@shared/components/filters/popup-filter/model/filter-wrapper.model'; +import { FilterGroup } from '@shared/components/filters/popup-filter/model/filter-wrapper.model'; export function processFilters(oldFilters: FilterModel[], newFilters: FilterModel[]) { copySettings(oldFilters, newFilters); @@ -159,7 +159,7 @@ export const addedDateChecker = (dw: DossierWrapper, filter: FilterModel) => dw. export const dossierApproverChecker = (dw: DossierWrapper, filter: FilterModel) => dw.approverIds.includes(filter.key); -export function getFilteredEntities(entities: T[], filters: FilterWrapper[]) { +export function getFilteredEntities(entities: T[], filters: FilterGroup[]) { const filteredEntities: T[] = []; for (const entity of entities) { let add = true; 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 59f0d5177..a60ebcca1 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/page-header/page-header.component.html b/apps/red-ui/src/app/modules/shared/components/page-header/page-header.component.html index 38eb57cda..ffa5724af 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 @@ { constructor(@Optional() readonly filterService: FilterService, @Optional() readonly searchService: SearchService) {} - get filters$(): Observable { - return this.filterService?.allFilters$.pipe(map(all => all.filter(f => f.icon))); + get filters$(): Observable { + return this.filterService?.filterGroups$.pipe(map(all => all.filter(f => f.icon))); } get showResetFilters$(): Observable { diff --git a/apps/red-ui/src/app/modules/shared/components/simple-doughnut-chart/simple-doughnut-chart.component.html b/apps/red-ui/src/app/modules/shared/components/simple-doughnut-chart/simple-doughnut-chart.component.html index 43f9c4c6c..94a662ac8 100644 --- a/apps/red-ui/src/app/modules/shared/components/simple-doughnut-chart/simple-doughnut-chart.component.html +++ b/apps/red-ui/src/app/modules/shared/components/simple-doughnut-chart/simple-doughnut-chart.component.html @@ -34,7 +34,7 @@ (click)="selectValue(val.key)" *ngFor="let val of config" [class.active]="filterService.filterChecked$('statusFilters', val.key) | async" - [class.filter-disabled]="(filterService.getFilter$('statusFilters') | async)?.length === 0" + [class.filter-disabled]="(filterService.getFilterModels$('statusFilters') | async)?.length === 0" > + + {{ tableHeaderLabel | translate: { length: (screenStateService.displayedLength$ | async) } }} + + + +
+ +
+ + +
+
diff --git a/apps/red-ui/src/app/modules/shared/components/table-header/table-header.component.scss b/apps/red-ui/src/app/modules/shared/components/table-header/table-header.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/apps/red-ui/src/app/modules/shared/components/table-header/table-header.component.ts b/apps/red-ui/src/app/modules/shared/components/table-header/table-header.component.ts new file mode 100644 index 000000000..e1b8b7b32 --- /dev/null +++ b/apps/red-ui/src/app/modules/shared/components/table-header/table-header.component.ts @@ -0,0 +1,16 @@ +import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; +import { TableColConfig } from '@shared/components/table-col-name/table-col-name.component'; +import { ScreenStateService } from '@shared/services/screen-state.service'; + +@Component({ + selector: 'redaction-table-header', + templateUrl: './table-header.component.html', + styleUrls: ['./table-header.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class TableHeaderComponent { + @Input() tableHeaderLabel: string; + @Input() tableColConfigs: TableColConfig[]; + + constructor(readonly screenStateService: ScreenStateService) {} +} diff --git a/apps/red-ui/src/app/modules/shared/directives/sync-width.directive.ts b/apps/red-ui/src/app/modules/shared/directives/sync-width.directive.ts index 0f1b5f46b..8a2398c4d 100644 --- a/apps/red-ui/src/app/modules/shared/directives/sync-width.directive.ts +++ b/apps/red-ui/src/app/modules/shared/directives/sync-width.directive.ts @@ -1,11 +1,4 @@ -import { - AfterViewInit, - Directive, - ElementRef, - HostListener, - Input, - OnDestroy -} from '@angular/core'; +import { AfterViewInit, Directive, ElementRef, HostListener, Input, OnDestroy } from '@angular/core'; import { debounce } from '@utils/debounce'; @Directive({ @@ -32,9 +25,8 @@ export class SyncWidthDirective implements AfterViewInit, OnDestroy { @debounce(10) matchWidth() { const headerItems = this._elementRef.nativeElement.children; - const tableRows = this._elementRef.nativeElement.parentElement.getElementsByClassName( - this.redactionSyncWidth - ); + // const tableRows = document.getElementsByClassName(this.redactionSyncWidth); + const tableRows = this._elementRef.nativeElement.parentElement.getElementsByClassName(this.redactionSyncWidth); if (!tableRows || !tableRows.length) { return; @@ -48,12 +40,8 @@ export class SyncWidthDirective implements AfterViewInit, OnDestroy { for (let idx = 0; idx < length - hasExtraColumns - 1; ++idx) { if (headerItems[idx]) { - headerItems[idx].style.width = `${ - tableRow.children[idx].getBoundingClientRect().width - }px`; - headerItems[idx].style.minWidth = `${ - tableRow.children[idx].getBoundingClientRect().width - }px`; + headerItems[idx].style.width = `${tableRow.children[idx].getBoundingClientRect().width}px`; + headerItems[idx].style.minWidth = `${tableRow.children[idx].getBoundingClientRect().width}px`; } } 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/pipes/sort-by.pipe.ts similarity index 83% rename from apps/red-ui/src/app/modules/shared/components/sort-pipe/sort-by.pipe.ts rename to apps/red-ui/src/app/modules/shared/pipes/sort-by.pipe.ts index bb2ade371..f9275b187 100644 --- a/apps/red-ui/src/app/modules/shared/components/sort-pipe/sort-by.pipe.ts +++ b/apps/red-ui/src/app/modules/shared/pipes/sort-by.pipe.ts @@ -1,5 +1,5 @@ import { Pipe, PipeTransform } from '@angular/core'; -import { SortingService } from '@services/sorting.service'; +import { SortingService } from '../../../services/sorting.service'; @Pipe({ name: 'sortBy' }) export class SortByPipe implements PipeTransform { 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 169e26655..b9f2f8ac2 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 @@ -1,7 +1,7 @@ import { ChangeDetectorRef, Injectable } from '@angular/core'; import { FilterModel } from '@shared/components/filters/popup-filter/model/filter.model'; import { getFilteredEntities, processFilters } from '@shared/components/filters/popup-filter/utils/filter-utils'; -import { FilterWrapper } from '@shared/components/filters/popup-filter/model/filter-wrapper.model'; +import { FilterGroup } 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'; import { BehaviorSubject, Observable } from 'rxjs'; @@ -9,7 +9,7 @@ import { distinctUntilChanged, filter, map } from 'rxjs/operators'; @Injectable() export class FilterService { - _allFilters$ = new BehaviorSubject([]); + _filterGroups$ = new BehaviorSubject([]); constructor( private readonly _screenStateService: ScreenStateService, @@ -17,70 +17,72 @@ export class FilterService { private readonly _changeDetector: ChangeDetectorRef ) {} - get filters() { - return Object.values(this._allFilters$.getValue()); + get filterGroups(): FilterGroup[] { + return Object.values(this._filterGroups$.getValue()); } - get showResetFilters$() { - return this.allFilters$.pipe( + get showResetFilters$(): Observable { + return this.filterGroups$.pipe( map(all => this._toFlatFilters(all)), map(f => !!f.find(el => el.checked)), distinctUntilChanged() ); } - filterChecked$(slug: string, key: string) { - const filters = this.getFilter$(slug); - return filters.pipe(map(all => all.find(f => f.key === key)?.checked)); + filterChecked$(slug: string, key: string): Observable { + return this.getFilterModels$(slug).pipe( + map(all => all.find(f => f.key === key)?.checked), + distinctUntilChanged() + ); } toggleFilter(slug: string, key: string) { - const filters = this.filters.find(f => f.slug === slug); + const filters = this.filterGroups.find(f => f.slug === slug); let found = filters.values.find(f => f.key === key); if (!found) found = filters.values.map(f => f.filters?.find(ff => ff.key === key))[0]; found.checked = !found.checked; - this._allFilters$.next(this.filters); - this.filterEntities(); + this._filterGroups$.next(this.filterGroups); + this.applyFilters(); } - filterEntities(): void { - const filtered = getFilteredEntities(this._screenStateService.allEntities, this.filters); - this._screenStateService.setFilteredEntities(filtered); + applyFilters(): void { + const filtered = getFilteredEntities(this._screenStateService.allEntities, this.filterGroups); + this._screenStateService.setDisplayedEntities(filtered); this._searchService.executeSearchImmediately(); this._changeDetector.detectChanges(); } - addFilter(value: FilterWrapper): void { - const oldFilters = this.getFilter(value.slug)?.values; - if (!oldFilters) return this._allFilters$.next([...this.filters, value]); + addFilterGroup(value: FilterGroup): void { + const oldFilters = this.getFilterGroup(value.slug)?.values; + if (!oldFilters) return this._filterGroups$.next([...this.filterGroups, value]); value.values = processFilters(oldFilters, value.values); - this._allFilters$.next([...this.filters.filter(f => f.slug !== value.slug), value]); + this._filterGroups$.next([...this.filterGroups.filter(f => f.slug !== value.slug), value]); } - getFilter(slug: string): FilterWrapper { - return this.filters.find(f => f?.slug === slug); + getFilterGroup(slug: string): FilterGroup { + return this.filterGroups.find(f => f?.slug === slug); } - getFilter$(slug: string): Observable { - return this.getFilterWrapper$(slug).pipe( + getFilterModels$(filterGroupSlug: string): Observable { + return this.getFilterGroup$(filterGroupSlug).pipe( filter(f => f !== null && f !== undefined), - map(f => f?.values) + map(f => f.values) ); } - getFilterWrapper$(slug: string): Observable { - return this.allFilters$.pipe(map(all => all.find(f => f?.slug === slug))); + getFilterGroup$(slug: string): Observable { + return this.filterGroups$.pipe(map(all => all.find(f => f?.slug === slug))); } - get allFilters$(): Observable { - return this._allFilters$.asObservable(); + get filterGroups$(): Observable { + return this._filterGroups$.asObservable(); } reset(): void { - this.filters.forEach(item => { + this.filterGroups.forEach(item => { item.values.forEach(child => { child.checked = false; child.indeterminate = false; @@ -90,11 +92,11 @@ export class FilterService { }); }); }); - this._allFilters$.next(this.filters); - this.filterEntities(); + this._filterGroups$.next(this.filterGroups); + this.applyFilters(); } - private _toFlatFilters(entities: FilterWrapper[]): FilterModel[] { + private _toFlatFilters(entities: FilterGroup[]): FilterModel[] { const flatChildren = (filters: FilterModel[]) => (filters ?? []).reduce((acc, f) => [...acc, ...(f?.filters ?? [])], []); return entities.reduce((acc, f) => [...acc, ...f.values, ...flatChildren(f.values)], []); 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 80cc99002..d53fd4e83 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 @@ -4,78 +4,102 @@ import { distinctUntilChanged, map } from 'rxjs/operators'; @Injectable() export class ScreenStateService { - allEntities$ = new BehaviorSubject([]); - filteredEntities$ = new BehaviorSubject([]); - displayedEntities$ = new BehaviorSubject([]); - selectedEntities$ = new BehaviorSubject([]); + _allEntities$ = new BehaviorSubject([]); + _displayedEntities$ = new BehaviorSubject([]); + _selectedEntities$ = new BehaviorSubject([]); + + // constructor() { + // setInterval(() => { + // console.log('All entities subs: ', this._allEntities$.observers); + // console.log('Displayed entities subs: ', this._displayedEntities$.observers); + // console.log('Selected entities subs: ', this._selectedEntities$.observers); + // }, 10000); + // } get allEntities(): T[] { - return Object.values(this.allEntities$.getValue()); - } - - get filteredEntities(): T[] { - return Object.values(this.filteredEntities$.getValue()); + return Object.values(this._allEntities$.getValue()); } get selectedEntities(): T[] { - return Object.values(this.selectedEntities$.getValue()); + return Object.values(this._selectedEntities$.getValue()); } get displayedEntities(): T[] { - return Object.values(this.displayedEntities$.getValue()); + return Object.values(this._displayedEntities$.getValue()); + } + + get allEntities$(): Observable { + return this._allEntities$.asObservable(); + } + + get selectedEntities$(): Observable { + return this._selectedEntities$.asObservable(); + } + + get displayedEntities$(): Observable { + return this._displayedEntities$.asObservable(); } map(func: (state: T[]) => K): Observable { - return this.allEntities$.asObservable().pipe( + return this.allEntities$.pipe( map((state: T[]) => func(state)), distinctUntilChanged() ); } setEntities(newEntities: Partial): void { - this.allEntities$.next(newEntities); - } - - setFilteredEntities(newEntities: Partial): void { - this.filteredEntities$.next(newEntities); + this._allEntities$.next(newEntities); } setSelectedEntities(newEntities: Partial): void { - this.selectedEntities$.next(newEntities); + this._selectedEntities$.next(newEntities); } setDisplayedEntities(newEntities: Partial): void { - this.displayedEntities$.next(newEntities); + this._displayedEntities$.next(newEntities); } get noData$(): Observable { - return this.allEntitiesLength$.pipe(map(length => length === 0)); + return this.allEntitiesLength$.pipe( + map(length => length === 0), + distinctUntilChanged() + ); } /** * Returns the length of all entities */ get allEntitiesLength$(): Observable { - return this.allEntities$.pipe(map(all => all?.length ?? 0)); + return this.allEntities$.pipe( + map(all => all?.length ?? 0), + distinctUntilChanged() + ); } /** * Returns the length of the currently displayed entities */ get displayedLength$(): Observable { - return this.displayedEntities$.pipe(map(all => all?.length ?? 0)); + return this.displayedEntities$.pipe( + map(all => all?.length ?? 0), + distinctUntilChanged() + ); } /** * Returns the length of the selected entities */ get selectedLength$(): Observable { - return this.selectedEntities$.pipe(map(all => all?.length ?? 0)); + return this.selectedEntities$.pipe( + map(all => all?.length ?? 0), + distinctUntilChanged() + ); } get areAllEntitiesSelected$(): Observable { return combineLatest([this.displayedLength$, this.selectedLength$]).pipe( - map(([displayedLength, selectedLength]) => displayedLength && displayedLength === selectedLength) + map(([displayedLength, selectedLength]) => displayedLength && displayedLength === selectedLength), + distinctUntilChanged() ); } @@ -83,7 +107,10 @@ export class ScreenStateService { * Indicates that some entities are selected. If all are selected this returns true */ get areSomeEntitiesSelected$(): Observable { - return this.selectedLength$.pipe(map(value => value > 0)); + return this.selectedLength$.pipe( + map(value => value > 0), + distinctUntilChanged() + ); } /** @@ -91,7 +118,8 @@ export class ScreenStateService { */ get notAllEntitiesSelected$(): Observable { return combineLatest([this.areAllEntitiesSelected$, this.areSomeEntitiesSelected$]).pipe( - map(([allEntitiesAreSelected, someEntitiesAreSelected]) => !allEntitiesAreSelected && someEntitiesAreSelected) + map(([allEntitiesAreSelected, someEntitiesAreSelected]) => !allEntitiesAreSelected && someEntitiesAreSelected), + distinctUntilChanged() ); } @@ -114,7 +142,6 @@ export class ScreenStateService { logCurrentState(): void { console.log('Entities', this.allEntities); console.log('Displayed', this.displayedEntities); - console.log('Filtered', this.filteredEntities); console.log('Selected', this.selectedEntities); } 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 78fbe8a0d..d3ef32824 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 @@ -29,14 +29,9 @@ export class SearchService { } executeSearchImmediately(): void { - const displayed = this._screenStateService.filteredEntities.length - ? this._screenStateService.filteredEntities - : this._screenStateService.allEntities; - - if (!this._searchKey) { - return this._screenStateService.setDisplayedEntities(displayed); - } + if (!this._searchKey) return; + const displayed = this._screenStateService.displayedEntities; this._screenStateService.setDisplayedEntities( displayed.filter(entity => this._searchField(entity).toLowerCase().includes(this._searchValue)) ); 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 d9f61970b..87a37d79e 100644 --- a/apps/red-ui/src/app/modules/shared/shared.module.ts +++ b/apps/red-ui/src/app/modules/shared/shared.module.ts @@ -24,7 +24,7 @@ import { DictionaryAnnotationIconComponent } from './components/dictionary-annot import { HiddenActionComponent } from './components/hidden-action/hidden-action.component'; import { ConfirmationDialogComponent } from './dialogs/confirmation-dialog/confirmation-dialog.component'; import { EmptyStateComponent } from './components/empty-state/empty-state.component'; -import { SortByPipe } from './components/sort-pipe/sort-by.pipe'; +import { SortByPipe } from './pipes/sort-by.pipe'; import { RoundCheckboxComponent } from './components/checkbox/round-checkbox.component'; import { DateAdapter, MAT_DATE_FORMATS, MAT_DATE_LOCALE } from '@angular/material/core'; import { MomentDateAdapter } from '@angular/material-moment-adapter'; @@ -39,6 +39,7 @@ import { AssignUserDropdownComponent } from './components/assign-user-dropdown/a 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'; +import { TableHeaderComponent } from './components/table-header/table-header.component'; const buttons = [ChevronButtonComponent, CircleButtonComponent, FileDownloadBtnComponent, IconButtonComponent, UserButtonComponent]; @@ -73,9 +74,9 @@ const utils = [HumanizePipe, DatePipe, SyncWidthDirective, HasScrollbarDirective const modules = [MatConfigModule, TranslateModule, ScrollingModule, IconsModule, FormsModule, ReactiveFormsModule]; @NgModule({ - declarations: [...components, ...utils], + declarations: [...components, ...utils, TableHeaderComponent], imports: [CommonModule, ...modules, MonacoEditorModule], - exports: [...modules, ...components, ...utils], + exports: [...modules, ...components, ...utils, TableHeaderComponent], providers: [ { provide: DateAdapter, useClass: MomentDateAdapter, deps: [MAT_DATE_LOCALE] }, { From 334ef37a5ce8b0d9f8a54e7e2b914ef6bf3e0bd6 Mon Sep 17 00:00:00 2001 From: Dan Percic Date: Mon, 19 Jul 2021 22:55:30 +0300 Subject: [PATCH 6/6] use properties to hold observables, fix filter & search issues --- .../active-fields-listing.component.ts | 1 - ...-attributes-csv-import-dialog.component.ts | 1 - .../default-colors-screen.component.ts | 1 - .../dictionary-listing-screen.component.ts | 6 +- ...ier-attributes-listing-screen.component.ts | 1 - ...sier-templates-listing-screen.component.ts | 1 - ...ile-attributes-listing-screen.component.ts | 11 +- .../screens/trash/trash-screen.component.ts | 2 - .../user-listing-screen.component.ts | 7 +- .../dossier-details.component.html | 6 +- .../dossier-details.component.ts | 6 +- .../dossier-listing-details.component.ts | 4 +- .../dossier-listing-screen.component.ts | 20 +- .../dossier-overview-screen.component.ts | 4 +- .../shared/base/base-listing.component.ts | 12 +- .../popup-filter/popup-filter.component.ts | 19 +- .../quick-filters.component.html | 2 +- .../quick-filters/quick-filters.component.ts | 12 +- .../page-header/page-header.component.html | 2 +- .../page-header/page-header.component.ts | 14 +- .../simple-doughnut-chart.component.html | 4 +- .../simple-doughnut-chart.component.ts | 12 +- .../modules/shared/services/filter.service.ts | 77 +++----- .../shared/services/screen-state.service.ts | 184 ++++++++---------- .../modules/shared/services/search.service.ts | 35 +--- .../src/app/services/loading.service.ts | 13 +- 26 files changed, 196 insertions(+), 261 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 cdaceeb4d..5068b98b5 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 @@ -29,7 +29,6 @@ export class ActiveFieldsListingComponent extends BaseListingComponent im ngOnChanges(changes: SimpleChanges): void { if (changes.entities) { this.screenStateService.setEntities(this.entities); - this.screenStateService.setDisplayedEntities(this.entities); this.screenStateService.updateSelection(); } } 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 4de397f09..44cac87d1 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 @@ -97,7 +97,6 @@ export class FileAttributesCsvImportDialogComponent extends BaseListingComponent } this.screenStateService.setEntities(this.parseResult.meta.fields.map(field => this._buildAttribute(field))); - this.screenStateService.setDisplayedEntities(this.allEntities); this.activeFields = []; for (const entity of this.allEntities) { 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 03a7f0ec9..043eb25a8 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 @@ -66,7 +66,6 @@ export class DefaultColorsScreenComponent value: data[key] })); this.screenStateService.setEntities(entities); - this.screenStateService.setDisplayedEntities(entities); this._loadingService.stop(); } } 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 e9eaf4359..fd1fdd8db 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,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, Injector, 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 } from '@redaction/red-ui-http'; import { AppStateService } from '@state/app-state.service'; @@ -17,7 +17,7 @@ import { BaseListingComponent } from '@shared/base/base-listing.component'; import { AdminDialogService } from '../../services/admin-dialog.service'; const toChartConfig = (dict: TypeValueWrapper): DoughnutChartConfig => ({ - value: dict.entries ? dict.entries.length : 0, + value: dict.entries?.length ?? 0, color: dict.hexColor, label: dict.label, key: dict.type @@ -100,8 +100,6 @@ export class DictionaryListingScreenComponent extends BaseListingComponent diff --git a/apps/red-ui/src/app/modules/admin/screens/dossier-attributes-listing/dossier-attributes-listing-screen.component.ts b/apps/red-ui/src/app/modules/admin/screens/dossier-attributes-listing/dossier-attributes-listing-screen.component.ts index 8a2e518f9..fd9d9f10f 100644 --- a/apps/red-ui/src/app/modules/admin/screens/dossier-attributes-listing/dossier-attributes-listing-screen.component.ts +++ b/apps/red-ui/src/app/modules/admin/screens/dossier-attributes-listing/dossier-attributes-listing-screen.component.ts @@ -61,7 +61,6 @@ export class DossierAttributesListingScreenComponent extends BaseListingComponen this._loadingService.start(); const attributes = await this._dossierAttributesService.getConfig(); this.screenStateService.setEntities(attributes); - this.filterService.applyFilters(); this._loadingService.stop(); } } 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 2db3ceb3a..769f22100 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 @@ -54,7 +54,6 @@ export class DossierTemplatesListingScreenComponent extends BaseListingComponent this._loadingService.start(); this._appStateService.reset(); this.screenStateService.setEntities(this._appStateService.dossierTemplates); - this.filterService.applyFilters(); this._loadDossierTemplateStats(); this._loadingService.stop(); } 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 be078f557..c17f588f1 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 @@ -91,17 +91,16 @@ export class FileAttributesListingScreenComponent extends BaseListingComponent implemen this._loadingService.start(); await this.loadDossierTemplatesData(); - this.filterService.applyFilters(); this._loadingService.stop(); } @@ -74,6 +73,5 @@ export class TrashScreenComponent extends BaseListingComponent implemen const entities = this.screenStateService.allEntities.filter(e => !ids.includes(e.dossierId)); this.screenStateService.setEntities(entities); this.screenStateService.setSelectedEntities([]); - this.filterService.applyFilters(); } } 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 37ca2be04..4a6fdd703 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,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, Injector, OnInit, QueryList, ViewChildren } from '@angular/core'; +import { Component, Injector, OnInit, QueryList, ViewChildren } from '@angular/core'; import { PermissionsService } from '@services/permissions.service'; import { UserService } from '@services/user.service'; import { User, UserControllerService } from '@redaction/red-ui-http'; @@ -19,11 +19,11 @@ import { map } from 'rxjs/operators'; @Component({ templateUrl: './user-listing-screen.component.html', styleUrls: ['./user-listing-screen.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, providers: [FilterService, SearchService, ScreenStateService, SortingService] }) export class UserListingScreenComponent extends BaseListingComponent implements OnInit { protected readonly _primaryKey = 'userId'; + readonly canDeleteSelected$ = this._canDeleteSelected$; collapsedDetails = false; chartData: DoughnutChartConfig[] = []; @@ -43,7 +43,7 @@ export class UserListingScreenComponent extends BaseListingComponent imple super(_injector); } - get canDeleteSelected$(): Observable { + get _canDeleteSelected$(): Observable { const entities$ = this.screenStateService.selectedEntities$; return entities$.pipe(map(all => all.indexOf(this.userService.user) === -1)); } @@ -86,7 +86,6 @@ export class UserListingScreenComponent extends BaseListingComponent imple private async _loadData() { this.screenStateService.setEntities(await this._userControllerService.getAllUsers().toPromise()); await this.userService.loadAllUsers(); - this.filterService.applyFilters(); this._computeStats(); this._loadingService.stop(); } 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 9a0b8acae..dfca40153 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 @@ -41,7 +41,7 @@ [config]="documentsChartData" [radius]="63" [strokeWidth]="15" - [subtitle]="i18nKey + 'charts.documents-in-dossier'" + [subtitle]="'dossier-overview.dossier-details.charts.documents-in-dossier'" direction="row" >
@@ -49,8 +49,8 @@
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 4a65ccbaf..5ad0f88f5 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 @@ -9,7 +9,6 @@ import { UserService } from '@services/user.service'; import { User } from '@redaction/red-ui-http'; import { Toaster } from '../../../../services/toaster.service'; import { FilterService } from '../../../shared/services/filter.service'; -import { FileStatusWrapper } from '../../../../models/file/file-status.wrapper'; import { DossierAttributeWithValue } from '@models/dossier-attributes.model'; @Component({ @@ -18,7 +17,6 @@ import { DossierAttributeWithValue } from '@models/dossier-attributes.model'; styleUrls: ['./dossier-details.component.scss'] }) export class DossierDetailsComponent implements OnInit { - readonly i18nKey = 'dossier-overview.dossier-details.'; documentsChartData: DoughnutChartConfig[] = []; owner: User; editingOwner = false; @@ -27,11 +25,13 @@ export class DossierDetailsComponent implements OnInit { @Output() openDossierDictionaryDialog = new EventEmitter(); @Output() toggleCollapse = new EventEmitter(); + readonly needsWorkFilters$ = this.filterService.getFilterModels$('needsWorkFilters'); + constructor( readonly appStateService: AppStateService, readonly translateChartService: TranslateChartService, readonly permissionsService: PermissionsService, - readonly filterService: FilterService, + readonly filterService: FilterService, private readonly _changeDetectorRef: ChangeDetectorRef, private readonly _userService: UserService, private readonly _toaster: Toaster diff --git a/apps/red-ui/src/app/modules/dossier/components/dossier-listing-details/dossier-listing-details.component.ts b/apps/red-ui/src/app/modules/dossier/components/dossier-listing-details/dossier-listing-details.component.ts index 331a9722f..254a92de9 100644 --- a/apps/red-ui/src/app/modules/dossier/components/dossier-listing-details/dossier-listing-details.component.ts +++ b/apps/red-ui/src/app/modules/dossier/components/dossier-listing-details/dossier-listing-details.component.ts @@ -9,9 +9,9 @@ import { FilterService } from '@shared/services/filter.service'; styleUrls: ['./dossier-listing-details.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush }) -export class DossierListingDetailsComponent { +export class DossierListingDetailsComponent { @Input() dossiersChartData: DoughnutChartConfig[]; @Input() documentsChartData: DoughnutChartConfig[]; - constructor(readonly appStateService: AppStateService, readonly filterService: FilterService) {} + constructor(readonly appStateService: AppStateService, readonly filterService: FilterService) {} } 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 c2ea8eeb5..83fadd698 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,11 +1,10 @@ -import { Component, Injector, OnDestroy, OnInit, TemplateRef, ViewChild } from '@angular/core'; +import { AfterViewInit, ChangeDetectorRef, 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'; import { DoughnutChartConfig } from '@shared/components/simple-doughnut-chart/simple-doughnut-chart.component'; 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 { timer } from 'rxjs'; import { filter } from 'rxjs/operators'; @@ -38,7 +37,10 @@ const isLeavingScreen = event => event instanceof NavigationStart && event.url ! styleUrls: ['./dossier-listing-screen.component.scss'], providers: [FilterService, SearchService, ScreenStateService, SortingService] }) -export class DossierListingScreenComponent extends BaseListingComponent implements OnInit, OnDestroy, OnAttach, OnDetach { +export class DossierListingScreenComponent + extends BaseListingComponent + implements OnInit, AfterViewInit, OnDestroy, OnAttach, OnDetach +{ readonly itemSize = 95; protected readonly _primaryKey = 'dossierName'; buttonConfigs: ButtonConfig[] = [ @@ -78,7 +80,6 @@ export class DossierListingScreenComponent extends BaseListingComponent; constructor( - readonly permissionsService: PermissionsService, private readonly _translateChartService: TranslateChartService, private readonly _userService: UserService, private readonly _dialogService: DossiersDialogService, @@ -86,6 +87,7 @@ export class DossierListingScreenComponent extends BaseListingComponent quickFilters.reduce((acc, f) => acc || (f.checked && f.checker(dw)), false) }); - - this.filterService.applyFilters(); } private _createQuickFilters() { 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 980d64688..9bab99401 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 @@ -49,8 +49,8 @@ export class DossierOverviewScreenComponent dossierAttributes: DossierAttributeWithValue[] = []; @ViewChild(DossierDetailsComponent, { static: false }) private readonly _dossierDetailsComponent: DossierDetailsComponent; + private readonly _lastOpenedFileKey = 'Dossier-Recent-' + this.activeDossier.dossierId; private _lastScrollPosition: number; - private _lastOpenedFileKey = 'Dossier-Recent-' + this.activeDossier.dossierId; @ViewChild('needsWorkTemplate', { read: TemplateRef, static: true }) private readonly _needsWorkTemplate: TemplateRef; @@ -163,8 +163,6 @@ export class DossierOverviewScreenComponent this._loadEntitiesFromState(); this._computeAllFilters(); - this.filterService.applyFilters(); - this._dossierDetailsComponent?.calculateChartConfig(); this._changeDetectorRef.detectChanges(); } 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 ee31643b0..9a3d2e34c 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 @@ -15,11 +15,14 @@ export abstract class BaseListingComponent extends AutoUnsubscribeComponent i readonly scrollViewport: CdkVirtualScrollViewport; readonly permissionsService: PermissionsService; - readonly filterService: FilterService; + readonly filterService: FilterService; readonly sortingService: SortingService; readonly searchService: SearchService; readonly screenStateService: ScreenStateService; + readonly sortedDisplayedEntities$: Observable; + readonly noMatch$: Observable; + /** * Key used in the *trackBy* function with **ngFor* or **cdkVirtualFor* * and in the default sorting and as the search field @@ -36,6 +39,9 @@ export abstract class BaseListingComponent extends AutoUnsubscribeComponent i this.searchService = this._injector.get(SearchService); this.screenStateService = this._injector.get>(ScreenStateService); setTimeout(() => this.setInitialConfig()); + + this.sortedDisplayedEntities$ = this._sortedDisplayedEntities$; + this.noMatch$ = this._noMatch$; } setInitialConfig() { @@ -50,7 +56,7 @@ export abstract class BaseListingComponent extends AutoUnsubscribeComponent i super.ngOnDestroy(); } - get sortedDisplayedEntities$(): Observable { + private get _sortedDisplayedEntities$(): Observable { return this.screenStateService.displayedEntities$.pipe(map(entities => this.sortingService.defaultSort(entities))); } @@ -58,7 +64,7 @@ export abstract class BaseListingComponent extends AutoUnsubscribeComponent i return this.screenStateService.allEntities; } - get noMatch$(): Observable { + private get _noMatch$(): Observable { return combineLatest([this.screenStateService.allEntitiesLength$, this.screenStateService.displayedLength$]).pipe( map(([hasEntities, hasDisplayedEntities]) => hasEntities && !hasDisplayedEntities), distinctUntilChanged() 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 7cca79894..296f0219d 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,12 +1,4 @@ -import { - ChangeDetectorRef, - Component, - EventEmitter, - Input, - OnChanges, - Output, - TemplateRef -} from '@angular/core'; +import { ChangeDetectorRef, Component, EventEmitter, Input, OnChanges, Output, TemplateRef } from '@angular/core'; import { FilterModel } from './model/filter.model'; import { handleCheckedValue } from './utils/filter-utils'; import { MAT_CHECKBOX_DEFAULT_OPTIONS } from '@angular/material/checkbox'; @@ -42,10 +34,7 @@ export class PopupFilterComponent implements OnChanges { atLeastOneFilterIsExpandable = false; atLeastOneSecondaryFilterIsExpandable = false; - constructor( - private readonly _changeDetectorRef: ChangeDetectorRef, - private readonly _translateService: TranslateService - ) {} + constructor(private readonly _changeDetectorRef: ChangeDetectorRef, private readonly _translateService: TranslateService) {} get hasActiveFilters(): boolean { return !!this._allFilters.find(f => f.checked || f.indeterminate); @@ -57,9 +46,7 @@ export class PopupFilterComponent implements OnChanges { ngOnChanges(): void { this.atLeastOneFilterIsExpandable = !!this.primaryFilters?.find(f => this.isExpandable(f)); - this.atLeastOneSecondaryFilterIsExpandable = !!this.secondaryFilters?.find(f => - this.isExpandable(f) - ); + this.atLeastOneSecondaryFilterIsExpandable = !!this.secondaryFilters?.find(f => this.isExpandable(f)); } filterCheckboxClicked($event: any, filter: FilterModel, parent?: FilterModel) { 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 a60ebcca1..a35b5fef6 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 632f0350e..fa29fc3bc 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,14 +1,14 @@ -import { Component, EventEmitter, Output } from '@angular/core'; -import { FilterModel } from '../popup-filter/model/filter.model'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; import { FilterService } from '@shared/services/filter.service'; @Component({ selector: 'redaction-quick-filters', templateUrl: './quick-filters.component.html', - styleUrls: ['./quick-filters.component.scss'] + styleUrls: ['./quick-filters.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush }) -export class QuickFiltersComponent { - @Output() filtersChanged = new EventEmitter(); +export class QuickFiltersComponent { + readonly quickFilters$ = this.filterService.getFilterModels$('quickFilters'); - constructor(readonly filterService: FilterService) {} + constructor(readonly filterService: FilterService) {} } 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 ffa5724af..1c9ef9031 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 @@ { @Input() buttonConfigs: ButtonConfig[]; @Input() searchPlaceholder: string; - constructor(@Optional() readonly filterService: FilterService, @Optional() readonly searchService: SearchService) {} + readonly filters$ = this.filterService?.filterGroups$.pipe(map(all => all.filter(f => f.icon))); + readonly showResetFilters$ = this._showResetFilters$; - get filters$(): Observable { - return this.filterService?.filterGroups$.pipe(map(all => all.filter(f => f.icon))); - } + constructor(@Optional() readonly filterService: FilterService, @Optional() readonly searchService: SearchService) {} + + get _showResetFilters$(): Observable { + if (!this.filterService) return of(false); - get showResetFilters$(): Observable { const filtersLength$ = this.filters$.pipe( map(f => f.length), distinctUntilChanged() diff --git a/apps/red-ui/src/app/modules/shared/components/simple-doughnut-chart/simple-doughnut-chart.component.html b/apps/red-ui/src/app/modules/shared/components/simple-doughnut-chart/simple-doughnut-chart.component.html index 94a662ac8..30d59d04a 100644 --- a/apps/red-ui/src/app/modules/shared/components/simple-doughnut-chart/simple-doughnut-chart.component.html +++ b/apps/red-ui/src/app/modules/shared/components/simple-doughnut-chart/simple-doughnut-chart.component.html @@ -33,8 +33,8 @@