diff --git a/README.md b/README.md index ebaa94b68..881ba1d5f 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,5 @@ # Redaction -## Swagger Generated Code - -To re-generate http rune swagger - -YOu need swagger-codegen installed `brew install swagger-codegen` - -``` -BASE=https://dev-06.iqser.cloud/ -URL="$BASE"redaction-gateway-v1/v2/api-docs?group=redaction-gateway-v1 -rm -Rf /tmp/swagger -mkdir -p /tmp/swagger -swagger-codegen generate -i "$URL" -l typescript-angular -o /tmp/swagger -cd /tmp/swagger -``` - ## To Create a new Stack in rancher Goto rancher.iqser.com: Select Cluster `Development`, go to apps, click launch and select `Redaction` from the `dev` @@ -25,17 +10,25 @@ Add cloudflare certificate and specify a hostname to use `timo-redaction-dev.iqs ## Keycloak Staging Config -keycloak: -authServerUrl: 'https://redkc-staging.iqser.cloud/auth' -client: -secret: 'a4e8aa56-03b0-4e6b-b822-8ac1f41280c4' +- keycloak: + - authServerUrl: 'https://redkc-staging.iqser.cloud/auth' +- client: + - secret: 'a4e8aa56-03b0-4e6b-b822-8ac1f41280c4' ## Default Testing URL -`https://timo-redaction-dev.iqser.cloud/` -Hostname: +`https://dev-04.iqser.cloud/` -timo-redaction-dev.iqser.cloud +## Known errors + +- In case of CORS or redirect_uri errors follow these steps: + - Go to `.iqser.cloud/auth/admin/master/console` + - Login with `admin` and `admin1234` + - In the left menu go to `Clients` + - In the table click `redaction` + - Find `Valid Redirect URIs` input + - Under `/ui/*` add new value `http://localhost:4200/*` + - **Save** ## Test Users diff --git a/angular.json b/angular.json index bef6f9183..271a6fd05 100644 --- a/angular.json +++ b/angular.json @@ -1,6 +1,6 @@ { "cli": { - "analytics": "4b8eed12-a1e6-4b7a-9ea2-925b27941271" + "analytics": false }, "version": 1, "projects": { diff --git a/apps/red-ui/src/app/app.component.html b/apps/red-ui/src/app/app.component.html index ff64625af..20a8fecb2 100644 --- a/apps/red-ui/src/app/app.component.html +++ b/apps/red-ui/src/app/app.component.html @@ -1,3 +1,4 @@ + diff --git a/apps/red-ui/src/app/app.module.ts b/apps/red-ui/src/app/app.module.ts index a1d31741d..00d8fdf7b 100644 --- a/apps/red-ui/src/app/app.module.ts +++ b/apps/red-ui/src/app/app.module.ts @@ -36,9 +36,10 @@ import { KeycloakService } from 'keycloak-angular'; import { GeneralSettingsService } from '@services/general-settings.service'; import { BreadcrumbsComponent } from '@components/breadcrumbs/breadcrumbs.component'; import { UserPreferenceService } from '@services/user-preference.service'; +import { UserService } from '@services/user.service'; -export function httpLoaderFactory(httpClient: HttpClient): PruningTranslationLoader { - return new PruningTranslationLoader(httpClient, '/assets/i18n/', '.json'); +export function httpLoaderFactory(httpClient: HttpClient, configService: ConfigService): PruningTranslationLoader { + return new PruningTranslationLoader(httpClient, '/assets/i18n/', `.json?version=${configService.values.FRONTEND_APP_VERSION}`); } function cleanupBaseUrl(baseUrl: string) { @@ -76,7 +77,7 @@ const components = [AppComponent, AuthErrorComponent, NotificationsComponent, Sp loader: { provide: TranslateLoader, useFactory: httpLoaderFactory, - deps: [HttpClient], + deps: [HttpClient, ConfigService], }, compiler: { provide: TranslateCompiler, @@ -114,7 +115,7 @@ const components = [AppComponent, AuthErrorComponent, NotificationsComponent, Sp provide: APP_INITIALIZER, multi: true, useFactory: configurationInitializer, - deps: [KeycloakService, Title, ConfigService, GeneralSettingsService, LanguageService, UserPreferenceService], + deps: [KeycloakService, Title, ConfigService, GeneralSettingsService, LanguageService, UserService, UserPreferenceService], }, { provide: MissingTranslationHandler, @@ -136,6 +137,7 @@ const components = [AppComponent, AuthErrorComponent, NotificationsComponent, Sp export class AppModule { constructor(private readonly _router: Router, private readonly _route: ActivatedRoute) { this._configureKeyCloakRouteHandling(); + // this._test(); } private _configureKeyCloakRouteHandling() { @@ -152,4 +154,60 @@ export class AppModule { } }); } + + // private _test(){ + // + // const flatGerman = flatten(german); + // + // + // const flatEnglish = flatten(english); + // + // const tmfc = new TranslateMessageFormatCompiler(); + // + // + // + // for (const key of Object.keys(flatGerman)) { + // try { + // const result = tmfc.compile(flatGerman[key], 'de'); + // //console.log(result); + // } catch (e) { + // console.error('ERROR AT: ', flatGerman[key]); + // } + // } + // + // for (const key of Object.keys(flatEnglish)) { + // try { + // const result = tmfc.compile(flatEnglish[key], 'de'); + // //console.log(result); + // } catch (e) { + // console.error('ERROR AT: ', flatEnglish[key]); + // } + // } + // console.log('done'); + // } } + +// +// function flatten(data: any) { +// const result: any = {}; +// +// function recurse(cur: any, prop: any) { +// if (Object(cur) !== cur) { +// result[prop] = cur; +// } else if (Array.isArray(cur)) { +// let l = 0; +// for (let i = 0, l = cur.length; i < l; i++) recurse(cur[i], prop + '[' + i + ']'); +// if (l === 0) result[prop] = []; +// } else { +// let isEmpty = true; +// for (const p in cur) { +// isEmpty = false; +// recurse(cur[p], prop ? prop + '.' + p : p); +// } +// if (isEmpty && prop) result[prop] = {}; +// } +// } +// +// recurse(data, ''); +// return result; +// } diff --git a/apps/red-ui/src/app/components/base-screen/base-screen.component.html b/apps/red-ui/src/app/components/base-screen/base-screen.component.html index 534a5c4fb..1f7abb691 100644 --- a/apps/red-ui/src/app/components/base-screen/base-screen.component.html +++ b/apps/red-ui/src/app/components/base-screen/base-screen.component.html @@ -27,7 +27,6 @@ @@ -36,7 +35,6 @@ diff --git a/apps/red-ui/src/app/components/base-screen/base-screen.component.ts b/apps/red-ui/src/app/components/base-screen/base-screen.component.ts index 1771c2bdd..3392423a7 100644 --- a/apps/red-ui/src/app/components/base-screen/base-screen.component.ts +++ b/apps/red-ui/src/app/components/base-screen/base-screen.component.ts @@ -9,7 +9,6 @@ import { TranslateService } from '@ngx-translate/core'; import { SpotlightSearchAction } from '@components/spotlight-search/spotlight-search-action'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { filter, map, startWith } from 'rxjs/operators'; -import { DossiersService } from '@services/entity-services/dossiers.service'; import { shareDistinctLast } from '@iqser/common-ui'; import { BreadcrumbsService } from '@services/breadcrumbs.service'; @@ -36,26 +35,22 @@ export class BaseScreenComponent { routerLink: '/main/account', show: true, action: this.appStateService.reset, - showDot: () => false, }, { name: _('top-bar.navigation-items.my-account.children.admin'), routerLink: '/main/admin', show: this.currentUser.isManager || this.currentUser.isUserAdmin, action: this.appStateService.reset, - showDot: () => false, }, { name: _('top-bar.navigation-items.my-account.children.downloads'), routerLink: '/main/downloads', show: this.currentUser.isUser, - showDot: () => this.fileDownloadService.hasPendingDownloads, }, { name: _('top-bar.navigation-items.my-account.children.trash'), routerLink: '/main/admin/trash', show: this.currentUser.isManager, - showDot: () => false, }, ]; readonly searchActions: readonly SpotlightSearchAction[] = [ @@ -81,11 +76,9 @@ export class BaseScreenComponent { constructor( readonly appStateService: AppStateService, - readonly dossiersService: DossiersService, readonly userService: UserService, readonly userPreferenceService: UserPreferenceService, readonly titleService: Title, - readonly fileDownloadService: FileDownloadService, private readonly _router: Router, private readonly _translateService: TranslateService, readonly breadcrumbsService: BreadcrumbsService, diff --git a/apps/red-ui/src/app/components/breadcrumbs/breadcrumbs.component.html b/apps/red-ui/src/app/components/breadcrumbs/breadcrumbs.component.html index 00792193c..fdf4f4b27 100644 --- a/apps/red-ui/src/app/components/breadcrumbs/breadcrumbs.component.html +++ b/apps/red-ui/src/app/components/breadcrumbs/breadcrumbs.component.html @@ -9,7 +9,7 @@ this._loadData())) + .subscribe(); } downloadItem(download: DownloadStatus) { @@ -55,11 +60,11 @@ export class DownloadsListScreenComponent extends ListingComponent d.storageId); - await this.fileDownloadService.delete({ storageIds }).toPromise(); + await firstValueFrom(this.fileDownloadService.delete({ storageIds })); await this._loadData(); } private async _loadData() { - await this.fileDownloadService.loadAll().toPromise(); + await firstValueFrom(this.fileDownloadService.loadAll()); } } diff --git a/apps/red-ui/src/app/components/notifications/notifications.component.scss b/apps/red-ui/src/app/components/notifications/notifications.component.scss index ae5488b69..41d696e1c 100644 --- a/apps/red-ui/src/app/components/notifications/notifications.component.scss +++ b/apps/red-ui/src/app/components/notifications/notifications.component.scss @@ -1,9 +1,5 @@ @use 'variables'; -.mt-2 { - margin-top: 2px; -} - ::ng-deep .notifications-backdrop + .cdk-overlay-connected-position-bounding-box { right: 0 !important; @@ -20,6 +16,7 @@ .view-all { cursor: pointer; + &:hover { color: var(--iqser-primary); } diff --git a/apps/red-ui/src/app/components/notifications/notifications.component.ts b/apps/red-ui/src/app/components/notifications/notifications.component.ts index 963e9d5da..29b22b627 100644 --- a/apps/red-ui/src/app/components/notifications/notifications.component.ts +++ b/apps/red-ui/src/app/components/notifications/notifications.component.ts @@ -6,7 +6,7 @@ import { DossiersService } from '@services/entity-services/dossiers.service'; import { NotificationsService } from '@services/notifications.service'; import { Notification } from '@red/domain'; import { distinctUntilChanged, map, switchMap, tap } from 'rxjs/operators'; -import { BehaviorSubject, Observable, timer } from 'rxjs'; +import { BehaviorSubject, firstValueFrom, Observable, timer } from 'rxjs'; import { AutoUnsubscribe, List, shareLast } from '@iqser/common-ui'; import { CHANGED_CHECK_INTERVAL } from '@utils/constants'; @@ -63,12 +63,12 @@ export class NotificationsComponent extends AutoUnsubscribe implements OnInit { async markRead($event, notifications: List = this._notifications$.getValue().map(n => n.id), isRead = true): Promise { $event.stopPropagation(); - await this._notificationsService.toggleNotificationRead(notifications, isRead).toPromise(); + await firstValueFrom(this._notificationsService.toggleNotificationRead(notifications, isRead)); await this._loadData(); } private async _loadData(): Promise { - const notifications = await this._notificationsService.getNotifications(INCLUDE_SEEN).toPromise(); + const notifications = await firstValueFrom(this._notificationsService.getNotifications(INCLUDE_SEEN)); this._notifications$.next(notifications); } diff --git a/apps/red-ui/src/app/components/spotlight-search/spotlight-search.component.html b/apps/red-ui/src/app/components/spotlight-search/spotlight-search.component.html index 348cc56ae..1ffe83c77 100644 --- a/apps/red-ui/src/app/components/spotlight-search/spotlight-search.component.html +++ b/apps/red-ui/src/app/components/spotlight-search/spotlight-search.component.html @@ -1,30 +1,29 @@ -
- + - - -
- -
-
-
+ + +
+ +
+
+
- - -
+ + +
- - -
+ + diff --git a/apps/red-ui/src/app/guards/can-deactivate.guard.ts b/apps/red-ui/src/app/guards/can-deactivate.guard.ts index b92aabb6c..38a494c00 100644 --- a/apps/red-ui/src/app/guards/can-deactivate.guard.ts +++ b/apps/red-ui/src/app/guards/can-deactivate.guard.ts @@ -1,40 +1,34 @@ import { CanDeactivate } from '@angular/router'; -import { Directive, HostListener, Injectable } from '@angular/core'; -import { Observable } from 'rxjs'; -import { TranslateService } from '@ngx-translate/core'; +import { Injectable } from '@angular/core'; +import { map, Observable } from 'rxjs'; +import { ConfirmationDialogService, ConfirmOptions } from '@iqser/common-ui'; export interface ComponentCanDeactivate { - hasChanges: boolean; -} - -@Directive() -export abstract class ComponentHasChanges implements ComponentCanDeactivate { - abstract hasChanges: boolean; - - protected constructor(protected _translateService: TranslateService) {} - - @HostListener('window:beforeunload', ['$event']) - unloadNotification($event: any) { - if (this.hasChanges) { - // This message will be displayed in IE/Edge - $event.returnValue = this._translateService.instant('pending-changes-guard'); - } - } + changed: boolean; + valid?: boolean; + isLeavingPage?: boolean; + save: () => Promise; } @Injectable({ providedIn: 'root' }) export class PendingChangesGuard implements CanDeactivate { - constructor(private readonly _translateService: TranslateService) {} + constructor(private _dialogService: ConfirmationDialogService) {} canDeactivate(component: ComponentCanDeactivate): boolean | Observable { - // if there are no pending changes, just allow deactivation; else confirm first - return !component.hasChanges - ? true - : // NOTE: this warning message will only be shown when navigating elsewhere - // within your angular app; - // when navigating away from your angular app, - // the browser will show a generic warning message - // see http://stackoverflow.com/a/42207299/7307355 - confirm(this._translateService.instant('pending-changes-guard')); + if (component.changed) { + component.isLeavingPage = true; + + const dialogRef = this._dialogService.openDialog({ disableConfirm: component.valid === false }); + return dialogRef.afterClosed().pipe( + map(result => { + if (result === ConfirmOptions.CONFIRM) { + component.save(); + } + component.isLeavingPage = false; + return !!result; + }), + ); + } + return true; } } diff --git a/apps/red-ui/src/app/guards/dossier-files-guard.ts b/apps/red-ui/src/app/guards/dossier-files-guard.ts index 5dd539adc..bf08f4737 100644 --- a/apps/red-ui/src/app/guards/dossier-files-guard.ts +++ b/apps/red-ui/src/app/guards/dossier-files-guard.ts @@ -3,6 +3,7 @@ import { ActivatedRouteSnapshot, CanActivate, Router } from '@angular/router'; import { DossiersService } from '@services/entity-services/dossiers.service'; import { FilesMapService } from '@services/entity-services/files-map.service'; import { FilesService } from '@services/entity-services/files.service'; +import { firstValueFrom } from 'rxjs'; @Injectable({ providedIn: 'root' }) export class DossierFilesGuard implements CanActivate { @@ -22,7 +23,7 @@ export class DossierFilesGuard implements CanActivate { } if (!this._filesMapService.has(dossierId)) { - await this._filesService.loadAll(dossierId).toPromise(); + await firstValueFrom(this._filesService.loadAll(dossierId)); } return true; } diff --git a/apps/red-ui/src/app/guards/dossiers.guard.ts b/apps/red-ui/src/app/guards/dossiers.guard.ts index be0c1550e..66a37f509 100644 --- a/apps/red-ui/src/app/guards/dossiers.guard.ts +++ b/apps/red-ui/src/app/guards/dossiers.guard.ts @@ -2,6 +2,7 @@ import { Injectable } from '@angular/core'; import { CanActivate, Router } from '@angular/router'; import { DossiersService } from '@services/entity-services/dossiers.service'; import { TranslateService } from '@ngx-translate/core'; +import { firstValueFrom } from 'rxjs'; @Injectable({ providedIn: 'root' }) export class DossiersGuard implements CanActivate { @@ -12,7 +13,7 @@ export class DossiersGuard implements CanActivate { ) {} async canActivate(): Promise { - await this._dossiersService.loadAll().toPromise(); + await firstValueFrom(this._dossiersService.loadAll()); return true; } } diff --git a/apps/red-ui/src/app/i18n/language.service.ts b/apps/red-ui/src/app/i18n/language.service.ts index d7a5b0c98..6a89894a6 100644 --- a/apps/red-ui/src/app/i18n/language.service.ts +++ b/apps/red-ui/src/app/i18n/language.service.ts @@ -1,6 +1,7 @@ import { Injectable } from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; import { UserPreferenceService } from '@services/user-preference.service'; +import { firstValueFrom } from 'rxjs'; @Injectable({ providedIn: 'root', @@ -8,6 +9,7 @@ import { UserPreferenceService } from '@services/user-preference.service'; export class LanguageService { constructor(private readonly _translateService: TranslateService, private readonly _userPreferenceService: UserPreferenceService) { _translateService.addLangs(['en', 'de']); + _translateService.setDefaultLang('en'); } get currentLanguage() { @@ -26,12 +28,12 @@ export class LanguageService { } document.documentElement.lang = defaultLang; this._translateService.setDefaultLang(defaultLang); - this._translateService.use(defaultLang).toPromise().then(); + firstValueFrom(this._translateService.use(defaultLang)).then(); } async changeLanguage(language: string) { await this._userPreferenceService.saveLanguage(language); document.documentElement.lang = language; - await this._translateService.use(language).toPromise(); + await firstValueFrom(this._translateService.use(language)); } } diff --git a/apps/red-ui/src/app/models/file/annotation.permissions.ts b/apps/red-ui/src/app/models/file/annotation.permissions.ts index ca2f1f911..1431b3e0c 100644 --- a/apps/red-ui/src/app/models/file/annotation.permissions.ts +++ b/apps/red-ui/src/app/models/file/annotation.permissions.ts @@ -1,6 +1,6 @@ import { AnnotationWrapper } from './annotation.wrapper'; -import { isArray } from 'rxjs/internal-compatibility'; import { User } from '@red/domain'; +import { isArray } from 'lodash'; export class AnnotationPermissions { canUndo = true; @@ -14,6 +14,7 @@ export class AnnotationPermissions { canChangeLegalBasis = true; canResizeAnnotation = true; canRecategorizeImage = true; + canForceHint = true; static forUser(isApprover: boolean, user: User, annotations: AnnotationWrapper | AnnotationWrapper[]) { if (!isArray(annotations)) { @@ -29,12 +30,13 @@ export class AnnotationPermissions { permissions.canAcceptSuggestion = isApprover && (annotation.isSuggestion || annotation.isDeclinedSuggestion); permissions.canRejectSuggestion = isApprover && annotation.isSuggestion; + permissions.canForceHint = annotation.isIgnoredHint; permissions.canForceRedaction = annotation.isSkipped && !annotation.isFalsePositive; permissions.canAcceptRecommendation = annotation.isRecommendation; permissions.canMarkAsFalsePositive = annotation.canBeMarkedAsFalsePositive; - permissions.canRemoveOrSuggestToRemoveOnlyHere = annotation.isRedacted; + permissions.canRemoveOrSuggestToRemoveOnlyHere = annotation.isRedacted || annotation.isHint; permissions.canRemoveOrSuggestToRemoveFromDictionary = annotation.isModifyDictionary && (annotation.isRedacted || annotation.isSkipped || annotation.isHint); @@ -42,7 +44,9 @@ export class AnnotationPermissions { permissions.canRecategorizeImage = (annotation.isImage && !annotation.isSuggestion) || annotation.isSuggestionRecategorizeImage; permissions.canResizeAnnotation = - ((annotation.isRedacted || annotation.isImage) && !annotation.isSuggestion) || annotation.isSuggestionResize; + ((annotation.isRedacted || annotation.isImage) && !annotation.isSuggestion) || + annotation.isSuggestionResize || + annotation.isRecommendation; summedPermissions._merge(permissions); } diff --git a/apps/red-ui/src/app/models/file/annotation.wrapper.ts b/apps/red-ui/src/app/models/file/annotation.wrapper.ts index 51a75e509..6751a0812 100644 --- a/apps/red-ui/src/app/models/file/annotation.wrapper.ts +++ b/apps/red-ui/src/app/models/file/annotation.wrapper.ts @@ -12,6 +12,7 @@ export type AnnotationSuperType = | 'suggestion-remove-dictionary' | 'suggestion-add' | 'suggestion-remove' + | 'ignored-hint' | 'skipped' | 'redaction' | 'manual-redaction' @@ -43,7 +44,9 @@ export class AnnotationWrapper { legalBasisChangeValue?: string; resizing?: boolean; rectangle?: boolean; + hintDictionary?: boolean; section?: string; + reference: Array; manual?: boolean; @@ -87,7 +90,7 @@ export class AnnotationWrapper { } get isSuperTypeBasedColor() { - return this.isSkipped || this.isSuggestion || this.isDeclinedSuggestion; + return this.isSkipped || this.isSuggestion || this.isDeclinedSuggestion || this.isIgnoredHint; } get isSkipped() { @@ -106,6 +109,20 @@ export class AnnotationWrapper { return this.recategorizationType || this.typeValue; } + get topLevelFilter() { + return ( + this.superType !== 'hint' && + this.superType !== 'redaction' && + this.superType !== 'recommendation' && + this.superType !== 'ignored-hint' && + this.superType !== 'skipped' + ); + } + + get filterKey() { + return this.topLevelFilter ? this.superType : this.superType + this.type; + } + get isManuallySkipped() { return this.isSkipped && this.manual; } @@ -113,7 +130,10 @@ export class AnnotationWrapper { get isFalsePositive() { return ( this.type?.toLowerCase() === 'false_positive' && - (this.superType === 'skipped' || this.superType === 'hint' || this.superType === 'redaction') + (this.superType === 'skipped' || + this.superType === 'hint' || + this.superType === 'ignored-hint' || + this.superType === 'redaction') ); } @@ -133,6 +153,10 @@ export class AnnotationWrapper { return this.superType === 'hint'; } + get isIgnoredHint() { + return this.superType === 'ignored-hint'; + } + get isRedacted() { return this.superType === 'redaction' || this.superType === 'manual-redaction'; } @@ -236,12 +260,14 @@ export class AnnotationWrapper { annotationWrapper.manual = redactionLogEntry.manual; annotationWrapper.engines = redactionLogEntry.engines; annotationWrapper.section = redactionLogEntry.section; + annotationWrapper.reference = redactionLogEntry.reference || []; annotationWrapper.rectangle = redactionLogEntry.rectangle; annotationWrapper.hasBeenResized = redactionLogEntry.hasBeenResized; annotationWrapper.hasBeenRecategorized = redactionLogEntry.hasBeenRecategorized; annotationWrapper.hasLegalBasisChanged = redactionLogEntry.hasLegalBasisChanged; annotationWrapper.hasBeenForced = redactionLogEntry.hasBeenForced; annotationWrapper.hasBeenRemovedByManualOverride = redactionLogEntry.hasBeenRemovedByManualOverride; + annotationWrapper.hintDictionary = redactionLogEntry.hintDictionary; this._createContent(annotationWrapper, redactionLogEntry); this._setSuperType(annotationWrapper, redactionLogEntry); @@ -259,11 +285,7 @@ export class AnnotationWrapper { private static _setSuperType(annotationWrapper: AnnotationWrapper, redactionLogEntryWrapper: RedactionLogEntryWrapper) { if (redactionLogEntryWrapper.recommendation) { - if (redactionLogEntryWrapper.redacted) { - annotationWrapper.superType = 'recommendation'; - } else { - annotationWrapper.superType = 'skipped'; - } + annotationWrapper.superType = 'recommendation'; return; } @@ -280,7 +302,7 @@ export class AnnotationWrapper { if (redactionLogEntryWrapper.status === 'REQUESTED') { annotationWrapper.superType = 'suggestion-force-redaction'; } else if (redactionLogEntryWrapper.status === 'APPROVED') { - annotationWrapper.superType = 'redaction'; + annotationWrapper.superType = redactionLogEntryWrapper.hint ? 'hint' : 'redaction'; } else { annotationWrapper.superType = 'skipped'; } @@ -408,6 +430,11 @@ export class AnnotationWrapper { if (!annotationWrapper.superType) { annotationWrapper.superType = annotationWrapper.redaction ? 'redaction' : annotationWrapper.hint ? 'hint' : 'skipped'; } + if (annotationWrapper.superType === 'skipped') { + if (redactionLogEntryWrapper.hintDictionary) { + annotationWrapper.superType = 'ignored-hint'; + } + } } private static _createContent(annotationWrapper: AnnotationWrapper, entry: RedactionLogEntryWrapper) { diff --git a/apps/red-ui/src/app/models/file/file-data.model.ts b/apps/red-ui/src/app/models/file/file-data.model.ts index 1c0d3b920..16727d14b 100644 --- a/apps/red-ui/src/app/models/file/file-data.model.ts +++ b/apps/red-ui/src/app/models/file/file-data.model.ts @@ -1,36 +1,48 @@ -import { Dictionary, File, IRedactionLog, IRedactionLogEntry, IViewedPage, User, ViewMode } from '@red/domain'; +import { Dictionary, File, IRedactionLog, IRedactionLogEntry, IViewedPage, ViewMode } from '@red/domain'; import { AnnotationWrapper } from './annotation.wrapper'; import { RedactionLogEntryWrapper } from './redaction-log-entry.wrapper'; import * as moment from 'moment'; - -export class AnnotationData { - visibleAnnotations: AnnotationWrapper[]; - allAnnotations: AnnotationWrapper[]; -} +import { BehaviorSubject } from 'rxjs'; export class FileDataModel { static readonly DELTA_VIEW_TIME = 10 * 60 * 1000; // 10 minutes; + allAnnotations: AnnotationWrapper[]; + readonly hasChangeLog$ = new BehaviorSubject(false); + readonly blob$ = new BehaviorSubject(undefined); + readonly file$ = new BehaviorSubject(undefined); - hasChangeLog: boolean; + constructor( + private readonly _file: File, + private readonly _blob: Blob, + private _redactionLog: IRedactionLog, + public viewedPages?: IViewedPage[], + private _dictionaryData?: { [p: string]: Dictionary }, + private _areDevFeaturesEnabled?: boolean, + ) { + this.file$.next(_file); + this.blob$.next(_blob); + this._buildAllAnnotations(); + } - constructor(public file: File, public fileData: Blob, public redactionLog: IRedactionLog, public viewedPages?: IViewedPage[]) {} + get file(): File { + return this.file$.value; + } - getAnnotations( - dictionaryData: { [p: string]: Dictionary }, - currentUser: User, - viewMode: ViewMode, - areDevFeaturesEnabled: boolean, - ): AnnotationData { - const entries: RedactionLogEntryWrapper[] = this._convertData(); - let allAnnotations = entries - .map(entry => AnnotationWrapper.fromData(entry)) - .filter(ann => ann.manual || !this.file.excludedPages.includes(ann.pageNumber)); + set file(file: File) { + this.file$.next(file); + } - if (!areDevFeaturesEnabled) { - allAnnotations = allAnnotations.filter(annotation => !annotation.isFalsePositive); - } + get redactionLog(): IRedactionLog { + return this._redactionLog; + } - const visibleAnnotations = allAnnotations.filter(annotation => { + set redactionLog(redactionLog: IRedactionLog) { + this._redactionLog = redactionLog; + this._buildAllAnnotations(); + } + + getVisibleAnnotations(viewMode: ViewMode) { + return this.allAnnotations.filter(annotation => { if (viewMode === 'STANDARD') { return !annotation.isChangeLogRemoved; } else if (viewMode === 'DELTA') { @@ -39,11 +51,30 @@ export class FileDataModel { return annotation.isRedacted; } }); + } - return { - visibleAnnotations: visibleAnnotations, - allAnnotations: allAnnotations, - }; + private _buildAllAnnotations() { + const entries: RedactionLogEntryWrapper[] = this._convertData(); + + const previousAnnotations = this.allAnnotations || []; + this.allAnnotations = entries + .map(entry => AnnotationWrapper.fromData(entry)) + .filter(ann => ann.manual || !this._file.excludedPages.includes(ann.pageNumber)); + + if (!this._areDevFeaturesEnabled) { + this.allAnnotations = this.allAnnotations.filter(annotation => !annotation.isFalsePositive); + } + + this._setHiddenPropertyToNewAnnotations(this.allAnnotations, previousAnnotations); + } + + private _setHiddenPropertyToNewAnnotations(newAnnotations: AnnotationWrapper[], oldAnnotations: AnnotationWrapper[]) { + newAnnotations.forEach(newAnnotation => { + const oldAnnotation = oldAnnotations.find(a => a.annotationId === newAnnotation.annotationId); + if (oldAnnotation) { + newAnnotation.hidden = oldAnnotation.hidden; + } + }); } private _convertData(): RedactionLogEntryWrapper[] { @@ -55,6 +86,7 @@ export class FileDataModel { const redactionLogEntryWrapper: RedactionLogEntryWrapper = {}; Object.assign(redactionLogEntryWrapper, redactionLogEntry); redactionLogEntryWrapper.type = redactionLogEntry.type; + redactionLogEntryWrapper.hintDictionary = this._dictionaryData[redactionLogEntry.type].hint; this._isChangeLogEntry(redactionLogEntry, redactionLogEntryWrapper); @@ -101,11 +133,11 @@ export class FileDataModel { } private _isChangeLogEntry(redactionLogEntry: IRedactionLogEntry, wrapper: RedactionLogEntryWrapper) { - if (this.file.numberOfAnalyses > 1) { - redactionLogEntry.changes.sort((a, b) => moment(a.dateTime).valueOf() - moment(b.dateTime).valueOf()); + if (this._file.numberOfAnalyses > 1) { + const viableChanges = redactionLogEntry.changes.filter(c => c.analysisNumber > 1); + viableChanges.sort((a, b) => moment(a.dateTime).valueOf() - moment(b.dateTime).valueOf()); - const lastChange = - redactionLogEntry.changes.length >= 1 ? redactionLogEntry.changes[redactionLogEntry.changes.length - 1] : undefined; + const lastChange = viableChanges.length >= 1 ? viableChanges[viableChanges.length - 1] : undefined; const page = redactionLogEntry.positions?.[0].page; const viewedPage = this.viewedPages.filter(p => p.page === page).pop(); @@ -114,14 +146,14 @@ export class FileDataModel { if (viewedPage) { const viewTime = moment(viewedPage.viewedTime).valueOf() - FileDataModel.DELTA_VIEW_TIME; // these are all unseen changes - const relevantChanges = redactionLogEntry.changes.filter(change => moment(change.dateTime).valueOf() > viewTime); + const relevantChanges = viableChanges.filter(change => moment(change.dateTime).valueOf() > viewTime); // at least one unseen change if (relevantChanges.length > 0) { // at least 1 relevant change wrapper.changeLogType = relevantChanges[relevantChanges.length - 1].type; wrapper.isChangeLogEntry = true; viewedPage.showAsUnseen = moment(viewedPage.viewedTime).valueOf() < moment(lastChange.dateTime).valueOf(); - this.hasChangeLog = true; + this.hasChangeLog$.next(true); } else { // no relevant changes - hide removed anyway wrapper.isChangeLogEntry = false; diff --git a/apps/red-ui/src/app/models/file/redaction-log-entry.wrapper.ts b/apps/red-ui/src/app/models/file/redaction-log-entry.wrapper.ts index f3bcacbd8..a406aba68 100644 --- a/apps/red-ui/src/app/models/file/redaction-log-entry.wrapper.ts +++ b/apps/red-ui/src/app/models/file/redaction-log-entry.wrapper.ts @@ -11,6 +11,7 @@ export interface RedactionLogEntryWrapper { startOffset?: number; type?: string; rectangle?: boolean; + hintDictionary?: boolean; color?: Array; dictionaryEntry?: boolean; diff --git a/apps/red-ui/src/app/modules/account/account-routing.module.ts b/apps/red-ui/src/app/modules/account/account-routing.module.ts index 9c77d73a2..56164ffa9 100644 --- a/apps/red-ui/src/app/modules/account/account-routing.module.ts +++ b/apps/red-ui/src/app/modules/account/account-routing.module.ts @@ -3,7 +3,7 @@ import { RouterModule } from '@angular/router'; import { CompositeRouteGuard } from '@iqser/common-ui'; import { AuthGuard } from '../auth/auth.guard'; import { RedRoleGuard } from '../auth/red-role.guard'; -import { AppStateGuard } from '../../state/app-state.guard'; +import { AppStateGuard } from '@state/app-state.guard'; import { BaseAccountScreenComponent } from './base-account-screen/base-account-screen-component'; const routes = [ diff --git a/apps/red-ui/src/app/modules/account/screens/notifications/constants.ts b/apps/red-ui/src/app/modules/account/screens/notifications/constants.ts deleted file mode 100644 index a6649c927..000000000 --- a/apps/red-ui/src/app/modules/account/screens/notifications/constants.ts +++ /dev/null @@ -1,43 +0,0 @@ -export const NotificationCategories = { - inAppNotifications: 'inAppNotifications', - emailNotifications: 'emailNotifications', -} as const; - -export const NotificationCategoriesValues = Object.values(NotificationCategories); - -export const DossierNotificationsTypes = { - dossierOwnerSet: 'DOSSIER_OWNER_SET', - dossierOwnerRemoved: 'DOSSIER_OWNER_REMOVED', - userBecomseDossierMember: 'USER_BECOMES_DOSSIER_MEMBER', - userRemovedAsDossierMember: 'USER_REMOVED_AS_DOSSIER_MEMBER', - userPromotedToApprover: 'USER_PROMOTED_TO_APPROVER', - userDegradedToReviewer: 'USER_DEGRADED_TO_REVIEWER', - dossierOwnerDeleted: 'DOSSIER_OWNER_DELETED', - dossierDeleted: 'DOSSIER_DELETED', -} as const; - -export const DossierNotificationsTypesValues = Object.values(DossierNotificationsTypes); - -export const DocumentNotificationsTypes = { - assignReviewer: 'ASSIGN_REVIEWER', - assignApprover: 'ASSIGN_APPROVER', - unassignedFromFile: 'UNASSIGNED_FROM_FILE', - // documentUnderReview: 'DOCUMENT_UNDER_REVIEW', - // documentUnderApproval: 'DOCUMENT_UNDER_APPROVAL', - documentApproved: 'DOCUMENT_APPROVED', -} as const; - -export const DocumentNotificationsTypesValues = Object.values(DocumentNotificationsTypes); - -export const OtherNotificationsTypes = { - downloadReady: 'DOWNLOAD_READY', -} as const; - -export const OtherNotificationsTypesValues = Object.values(OtherNotificationsTypes); - -export const NotificationGroupsKeys = ['dossier', 'document', 'other'] as const; -export const NotificationGroupsValues = [ - DossierNotificationsTypesValues, - DocumentNotificationsTypesValues, - OtherNotificationsTypesValues, -] as const; diff --git a/apps/red-ui/src/app/modules/account/screens/notifications/notifications-screen/notifications-screen.component.html b/apps/red-ui/src/app/modules/account/screens/notifications/notifications-screen/notifications-screen.component.html index 8c76795dc..cf0f4412e 100644 --- a/apps/red-ui/src/app/modules/account/screens/notifications/notifications-screen/notifications-screen.component.html +++ b/apps/red-ui/src/app/modules/account/screens/notifications/notifications-screen/notifications-screen.component.html @@ -1,4 +1,4 @@ -
+
@@ -7,25 +7,25 @@ }}
-
-
-
- - - {{ translations[type.toLocaleLowerCase()] | translate }} -
-
+
+ + + + + + +
-
-
+
+
{{ translations[preference] | translate }} @@ -36,7 +36,7 @@
-
diff --git a/apps/red-ui/src/app/modules/account/screens/notifications/notifications-screen/notifications-screen.component.ts b/apps/red-ui/src/app/modules/account/screens/notifications/notifications-screen/notifications-screen.component.ts index c2003cddb..a862021bd 100644 --- a/apps/red-ui/src/app/modules/account/screens/notifications/notifications-screen/notifications-screen.component.ts +++ b/apps/red-ui/src/app/modules/account/screens/notifications/notifications-screen/notifications-screen.component.ts @@ -2,10 +2,15 @@ import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; import { FormBuilder, FormGroup } from '@angular/forms'; import { notificationsTranslations } from '../../../translations/notifications-translations'; import { NotificationPreferencesService } from '../../../services/notification-preferences.service'; -import { LoadingService, Toaster } from '@iqser/common-ui'; +import { BaseFormComponent, LoadingService, Toaster } from '@iqser/common-ui'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; -import { NotificationCategoriesValues, NotificationGroupsKeys, NotificationGroupsValues } from '../constants'; -import { EmailNotificationScheduleTypesValues } from '@red/domain'; +import { + EmailNotificationScheduleTypesValues, + NotificationCategoriesValues, + NotificationGroupsKeys, + NotificationGroupsValues, +} from '@red/domain'; +import { firstValueFrom } from 'rxjs'; @Component({ selector: 'redaction-notifications-screen', @@ -13,26 +18,63 @@ import { EmailNotificationScheduleTypesValues } from '@red/domain'; styleUrls: ['./notifications-screen.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class NotificationsScreenComponent implements OnInit { +export class NotificationsScreenComponent extends BaseFormComponent implements OnInit { readonly emailNotificationScheduleTypes = EmailNotificationScheduleTypesValues; readonly notificationCategories = NotificationCategoriesValues; readonly notificationGroupsKeys = NotificationGroupsKeys; readonly notificationGroupsValues = NotificationGroupsValues; readonly translations = notificationsTranslations; - readonly formGroup: FormGroup = this._getForm(); - constructor( private readonly _toaster: Toaster, private readonly _formBuilder: FormBuilder, private readonly _loadingService: LoadingService, private readonly _notificationPreferencesService: NotificationPreferencesService, - ) {} + ) { + super(); + } async ngOnInit(): Promise { await this._initializeForm(); } + isCategoryActive(category: string) { + return this.form.get(`${category}Enabled`).value; + } + + setEmailNotificationType(type: string) { + this.form.get('emailNotificationType').setValue(type); + } + + getEmailNotificationType() { + return this.form.get('emailNotificationType').value; + } + + isPreferenceChecked(category: string, preference: string) { + return this.form.get(category).value.includes(preference); + } + + addRemovePreference(checked: boolean, category: string, preference: string) { + const preferences = this.form.get(category).value; + if (checked) { + preferences.push(preference); + } else { + const indexOfPreference = preferences.indexOf(preference); + preferences.splice(indexOfPreference, 1); + } + this.form.get(category).setValue(preferences); + } + + async save() { + this._loadingService.start(); + try { + await firstValueFrom(this._notificationPreferencesService.update(this.form.value)); + } catch (e) { + this._toaster.error(_('notifications-screen.error.generic')); + } + this._loadingService.stop(); + } + private _getForm(): FormGroup { return this._formBuilder.group({ inAppNotificationsEnabled: [undefined], @@ -43,48 +85,13 @@ export class NotificationsScreenComponent implements OnInit { }); } - isCategoryActive(category: string) { - return this.formGroup.get(`${category}Enabled`).value; - } - - setEmailNotificationType(type: string) { - this.formGroup.get('emailNotificationType').setValue(type); - } - - getEmailNotificationType() { - return this.formGroup.get('emailNotificationType').value; - } - - isPreferenceChecked(category: string, preference: string) { - return this.formGroup.get(category).value.includes(preference); - } - - addRemovePreference(checked: boolean, category: string, preference: string) { - const preferences = this.formGroup.get(category).value; - if (checked) { - preferences.push(preference); - } else { - const indexOfPreference = preferences.indexOf(preference); - preferences.splice(indexOfPreference, 1); - } - this.formGroup.get(category).setValue(preferences); - } - - async save() { - this._loadingService.start(); - try { - await this._notificationPreferencesService.update(this.formGroup.value).toPromise(); - } catch (e) { - this._toaster.error(_('notifications-screen.error.generic')); - } - this._loadingService.stop(); - } - private async _initializeForm() { this._loadingService.start(); - const notificationPreferences = await this._notificationPreferencesService.get().toPromise(); - this.formGroup.patchValue(notificationPreferences); + this.form = this._getForm(); + const notificationPreferences = await firstValueFrom(this._notificationPreferencesService.get()); + this.form.patchValue(notificationPreferences); + this.initialFormValue = JSON.parse(JSON.stringify(this.form.getRawValue())); this._loadingService.stop(); } diff --git a/apps/red-ui/src/app/modules/account/screens/notifications/notifications.module.ts b/apps/red-ui/src/app/modules/account/screens/notifications/notifications.module.ts index d7a134836..ef7874558 100644 --- a/apps/red-ui/src/app/modules/account/screens/notifications/notifications.module.ts +++ b/apps/red-ui/src/app/modules/account/screens/notifications/notifications.module.ts @@ -3,8 +3,9 @@ import { RouterModule } from '@angular/router'; import { CommonModule } from '@angular/common'; import { SharedModule } from '@shared/shared.module'; import { NotificationsScreenComponent } from './notifications-screen/notifications-screen.component'; +import { PendingChangesGuard } from '../../../../guards/can-deactivate.guard'; -const routes = [{ path: '', component: NotificationsScreenComponent }]; +const routes = [{ path: '', component: NotificationsScreenComponent, canDeactivate: [PendingChangesGuard] }]; @NgModule({ declarations: [NotificationsScreenComponent], diff --git a/apps/red-ui/src/app/modules/account/screens/user-profile/user-profile-screen/user-profile-screen.component.ts b/apps/red-ui/src/app/modules/account/screens/user-profile/user-profile-screen/user-profile-screen.component.ts index 8c3d896bf..421d54e39 100644 --- a/apps/red-ui/src/app/modules/account/screens/user-profile/user-profile-screen/user-profile-screen.component.ts +++ b/apps/red-ui/src/app/modules/account/screens/user-profile/user-profile-screen/user-profile-screen.component.ts @@ -2,13 +2,14 @@ import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser'; import { TranslateService } from '@ngx-translate/core'; -import { LoadingService } from '@iqser/common-ui'; +import { BaseFormComponent, LoadingService } from '@iqser/common-ui'; import { IProfile } from '@red/domain'; import { languagesTranslations } from '../../../translations/languages-translations'; -import { PermissionsService } from '../../../../../services/permissions.service'; -import { UserService } from '../../../../../services/user.service'; +import { PermissionsService } from '@services/permissions.service'; +import { UserService } from '@services/user.service'; import { ConfigService } from '../../../../../services/config.service'; import { LanguageService } from '../../../../../i18n/language.service'; +import { firstValueFrom } from 'rxjs'; @Component({ selector: 'redaction-user-profile-screen', @@ -16,8 +17,7 @@ import { LanguageService } from '../../../../../i18n/language.service'; styleUrls: ['./user-profile-screen.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class UserProfileScreenComponent implements OnInit { - readonly form: FormGroup = this._getForm(); +export class UserProfileScreenComponent extends BaseFormComponent implements OnInit { changePasswordUrl: SafeResourceUrl; translations = languagesTranslations; @@ -30,25 +30,16 @@ export class UserProfileScreenComponent implements OnInit { private readonly _configService: ConfigService, private readonly _languageService: LanguageService, private readonly _domSanitizer: DomSanitizer, - private readonly _translateService: TranslateService, private readonly _loadingService: LoadingService, + protected readonly _translateService: TranslateService, ) { + super(); this._loadingService.start(); - this.changePasswordUrl = this._domSanitizer.bypassSecurityTrustResourceUrl( `${this._configService.values.OAUTH_URL}/account/password`, ); } - private _getForm(): FormGroup { - return this._formBuilder.group({ - email: [undefined, [Validators.required, Validators.email]], - firstName: [undefined], - lastName: [undefined], - language: [undefined], - }); - } - get languageChanged(): boolean { return this._profileModel['language'] !== this.form.get('language').value; } @@ -82,24 +73,34 @@ export class UserProfileScreenComponent implements OnInit { } if (this.profileChanged) { - const value = this.form.value as IProfile; + const value = this.form.getRawValue() as IProfile; delete value.language; - await this._userService - .updateMyProfile({ + await firstValueFrom( + this._userService.updateMyProfile({ ...value, - }) - .toPromise(); + }), + ); await this._userService.loadCurrentUser(); - await this._userService.loadAll().toPromise(); + await firstValueFrom(this._userService.loadAll()); } this._initializeForm(); } + private _getForm(): FormGroup { + return this._formBuilder.group({ + email: [undefined, [Validators.required, Validators.email]], + firstName: [undefined], + lastName: [undefined], + language: [undefined], + }); + } + private _initializeForm(): void { try { + this.form = this._getForm(); this._profileModel = { email: this._userService.currentUser.email, firstName: this._userService.currentUser.firstName, @@ -111,6 +112,7 @@ export class UserProfileScreenComponent implements OnInit { this.form.get('email').disable(); } this.form.patchValue(this._profileModel, { emitEvent: false }); + this.initialFormValue = this.form.getRawValue(); } catch (e) { } finally { this._loadingService.stop(); diff --git a/apps/red-ui/src/app/modules/account/screens/user-profile/user-profile.module.ts b/apps/red-ui/src/app/modules/account/screens/user-profile/user-profile.module.ts index 64bf3d6f4..775a99569 100644 --- a/apps/red-ui/src/app/modules/account/screens/user-profile/user-profile.module.ts +++ b/apps/red-ui/src/app/modules/account/screens/user-profile/user-profile.module.ts @@ -3,8 +3,9 @@ import { RouterModule } from '@angular/router'; import { CommonModule } from '@angular/common'; import { SharedModule } from '@shared/shared.module'; import { UserProfileScreenComponent } from './user-profile-screen/user-profile-screen.component'; +import { PendingChangesGuard } from '../../../../guards/can-deactivate.guard'; -const routes = [{ path: '', component: UserProfileScreenComponent }]; +const routes = [{ path: '', component: UserProfileScreenComponent, canDeactivate: [PendingChangesGuard] }]; @NgModule({ declarations: [UserProfileScreenComponent], diff --git a/apps/red-ui/src/app/modules/account/services/notification-preferences.service.ts b/apps/red-ui/src/app/modules/account/services/notification-preferences.service.ts index acd23a718..0d875dfed 100644 --- a/apps/red-ui/src/app/modules/account/services/notification-preferences.service.ts +++ b/apps/red-ui/src/app/modules/account/services/notification-preferences.service.ts @@ -1,7 +1,7 @@ import { Injectable, Injector } from '@angular/core'; import { GenericService } from '@iqser/common-ui'; import { Observable, of } from 'rxjs'; -import { UserService } from '../../../services/user.service'; +import { UserService } from '@services/user.service'; import { EmailNotificationScheduleTypes, INotificationPreferences } from '@red/domain'; import { catchError } from 'rxjs/operators'; @@ -11,14 +11,6 @@ export class NotificationPreferencesService extends GenericService { - return super.get().pipe(catchError(() => of(this._defaultPreferences))); - } - - update(notificationPreferences: INotificationPreferences): Observable { - return super._post(notificationPreferences); - } - private get _defaultPreferences(): INotificationPreferences { return { emailNotificationType: EmailNotificationScheduleTypes.INSTANT, @@ -28,4 +20,12 @@ export class NotificationPreferencesService extends GenericService { + return super.get().pipe(catchError(() => of(this._defaultPreferences))); + } + + update(notificationPreferences: INotificationPreferences): Observable { + return super._post(notificationPreferences); + } } diff --git a/apps/red-ui/src/app/modules/admin/admin-routing.module.ts b/apps/red-ui/src/app/modules/admin/admin-routing.module.ts index 74ee574cc..4593549ea 100644 --- a/apps/red-ui/src/app/modules/admin/admin-routing.module.ts +++ b/apps/red-ui/src/app/modules/admin/admin-routing.module.ts @@ -3,7 +3,6 @@ import { AuthGuard } from '../auth/auth.guard'; import { CompositeRouteGuard } from '@iqser/common-ui'; import { RedRoleGuard } from '../auth/red-role.guard'; import { AppStateGuard } from '@state/app-state.guard'; -import { DossierTemplatesListingScreenComponent } from './screens/dossier-template-listing/dossier-templates-listing-screen.component'; import { DictionaryListingScreenComponent } from './screens/dictionary-listing/dictionary-listing-screen.component'; import { DictionaryOverviewScreenComponent } from './screens/dictionary-overview/dictionary-overview-screen.component'; import { PendingChangesGuard } from '@guards/can-deactivate.guard'; @@ -21,6 +20,7 @@ import { DossierAttributesListingScreenComponent } from './screens/dossier-attri import { TrashScreenComponent } from './screens/trash/trash-screen.component'; import { GeneralConfigScreenComponent } from './screens/general-config/general-config-screen.component'; import { BaseAdminScreenComponent } from './base-admin-screen/base-admin-screen.component'; +import { BaseDossierTemplateScreenComponent } from './base-dossier-templates-screen/base-dossier-template-screen.component'; const routes: Routes = [ { path: '', redirectTo: 'dossier-templates', pathMatch: 'full' }, @@ -29,15 +29,25 @@ const routes: Routes = [ children: [ { path: '', - component: DossierTemplatesListingScreenComponent, + component: BaseAdminScreenComponent, canActivate: [CompositeRouteGuard], data: { routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard], }, + loadChildren: () => + import('./screens/dossier-templates-listing/dossier-templates-listing.module').then( + m => m.DossierTemplatesListingModule, + ), }, { path: ':dossierTemplateId', children: [ + { + path: 'info', + canActivate: [CompositeRouteGuard], + component: BaseDossierTemplateScreenComponent, + loadChildren: () => import('./screens/info/dossier-template-info.module').then(m => m.DossierTemplateInfoModule), + }, { path: 'dictionaries', children: [ @@ -111,11 +121,11 @@ const routes: Routes = [ }, { path: 'justifications', - component: BaseAdminScreenComponent, + component: BaseDossierTemplateScreenComponent, canActivate: [CompositeRouteGuard], loadChildren: () => import('./screens/justifications/justifications.module').then(m => m.JustificationsModule), }, - { path: '', redirectTo: 'dictionaries', pathMatch: 'full' }, + { path: '', redirectTo: 'info', pathMatch: 'full' }, ], }, ], @@ -164,6 +174,7 @@ const routes: Routes = [ path: 'general-config', component: GeneralConfigScreenComponent, canActivate: [CompositeRouteGuard], + canDeactivate: [PendingChangesGuard], data: { routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard], requiredRoles: ['RED_ADMIN'], diff --git a/apps/red-ui/src/app/modules/admin/admin-side-nav/admin-side-nav.component.ts b/apps/red-ui/src/app/modules/admin/admin-side-nav/admin-side-nav.component.ts index 8dd9f629d..db715d7b9 100644 --- a/apps/red-ui/src/app/modules/admin/admin-side-nav/admin-side-nav.component.ts +++ b/apps/red-ui/src/app/modules/admin/admin-side-nav/admin-side-nav.component.ts @@ -28,7 +28,7 @@ export class AdminSideNavComponent implements OnInit { settings: [ { screen: 'dossier-templates', - label: _('dossier-templates'), + label: _('dossier-templates.label'), hideIf: !this.currentUser.isManager && !this.currentUser.isAdmin, }, { @@ -50,6 +50,7 @@ export class AdminSideNavComponent implements OnInit { }, ], dossierTemplates: [ + { screen: 'info', label: _('dossier-template-info') }, { screen: 'dictionaries', label: _('dictionaries') }, { screen: 'rules', diff --git a/apps/red-ui/src/app/modules/admin/admin.module.ts b/apps/red-ui/src/app/modules/admin/admin.module.ts index 6ed502c5b..aee060360 100644 --- a/apps/red-ui/src/app/modules/admin/admin.module.ts +++ b/apps/red-ui/src/app/modules/admin/admin.module.ts @@ -3,7 +3,6 @@ import { CommonModule } from '@angular/common'; import { AdminRoutingModule } from './admin-routing.module'; import { RulesScreenComponent } from './screens/rules/rules-screen.component'; import { SharedModule } from '@shared/shared.module'; -import { DossierTemplatesListingScreenComponent } from './screens/dossier-template-listing/dossier-templates-listing-screen.component'; import { AuditScreenComponent } from './screens/audit/audit-screen.component'; import { DefaultColorsScreenComponent } from './screens/default-colors/default-colors-screen.component'; import { DictionaryListingScreenComponent } from './screens/dictionary-listing/dictionary-listing-screen.component'; @@ -13,13 +12,12 @@ import { FileAttributesListingScreenComponent } from './screens/file-attributes- import { LicenseInformationScreenComponent } from './screens/license-information/license-information-screen.component'; import { UserListingScreenComponent } from './screens/user-listing/user-listing-screen.component'; import { WatermarkScreenComponent } from './screens/watermark/watermark-screen.component'; -import { AdminBreadcrumbsComponent } from './components/breadcrumbs/admin-breadcrumbs.component'; -import { DossierTemplateActionsComponent } from './components/dossier-template-actions/dossier-template-actions.component'; +import { DossierTemplateBreadcrumbsComponent } from './components/dossier-template-breadcrumbs/dossier-template-breadcrumbs.component'; import { ColorPickerModule } from 'ngx-color-picker'; import { AddEditFileAttributeDialogComponent } from './dialogs/add-edit-file-attribute-dialog/add-edit-file-attribute-dialog.component'; import { AddEditDossierTemplateDialogComponent } from './dialogs/add-edit-dossier-template-dialog/add-edit-dossier-template-dialog.component'; import { AddEditDictionaryDialogComponent } from './dialogs/add-edit-dictionary-dialog/add-edit-dictionary-dialog.component'; -import { ConfirmDeleteFileAttributeDialogComponent } from './dialogs/confirm-delete-file-attribute-dialog/confirm-delete-file-attribute-dialog.component'; +import { ConfirmDeleteAttributeDialogComponent } from './dialogs/confirm-delete-attribute-dialog/confirm-delete-attribute-dialog.component'; import { EditColorDialogComponent } from './dialogs/edit-color-dialog/edit-color-dialog.component'; import { ComboChartComponent, ComboSeriesVerticalComponent } from './components/combo-chart'; import { NgxChartsModule } from '@swimlane/ngx-charts'; @@ -49,12 +47,14 @@ import { UploadDictionaryDialogComponent } from './dialogs/upload-dictionary-dia import { GeneralConfigFormComponent } from './screens/general-config/general-config-form/general-config-form.component'; import { SmtpFormComponent } from './screens/general-config/smtp-form/smtp-form.component'; import { FileAttributesConfigurationsDialogComponent } from './dialogs/file-attributes-configurations-dialog/file-attributes-configurations-dialog.component'; +import { SharedAdminModule } from './shared/shared-admin.module'; +import { BaseDossierTemplateScreenComponent } from './base-dossier-templates-screen/base-dossier-template-screen.component'; const dialogs = [ AddEditDossierTemplateDialogComponent, AddEditDictionaryDialogComponent, AddEditFileAttributeDialogComponent, - ConfirmDeleteFileAttributeDialogComponent, + ConfirmDeleteAttributeDialogComponent, EditColorDialogComponent, SmtpAuthDialogComponent, AddEditUserDialogComponent, @@ -66,7 +66,6 @@ const dialogs = [ ]; const screens = [ - DossierTemplatesListingScreenComponent, RulesScreenComponent, AuditScreenComponent, DefaultColorsScreenComponent, @@ -84,8 +83,7 @@ const screens = [ ]; const components = [ - AdminBreadcrumbsComponent, - DossierTemplateActionsComponent, + DossierTemplateBreadcrumbsComponent, ComboChartComponent, ComboSeriesVerticalComponent, UsersStatsComponent, @@ -94,14 +92,17 @@ const components = [ ResetPasswordComponent, UserDetailsComponent, BaseAdminScreenComponent, + BaseDossierTemplateScreenComponent, + GeneralConfigFormComponent, + SmtpFormComponent, ...dialogs, ...screens, ]; @NgModule({ - declarations: [...components, GeneralConfigFormComponent, SmtpFormComponent], + declarations: [...components], providers: [AdminDialogService, AuditService, DigitalSignatureService, LicenseReportService, RulesService, SmtpConfigService], - imports: [CommonModule, SharedModule, AdminRoutingModule, NgxChartsModule, ColorPickerModule, MonacoEditorModule], + imports: [CommonModule, SharedModule, AdminRoutingModule, SharedAdminModule, NgxChartsModule, ColorPickerModule, MonacoEditorModule], }) export class AdminModule {} diff --git a/apps/red-ui/src/app/modules/admin/base-admin-screen/base-admin-screen.component.html b/apps/red-ui/src/app/modules/admin/base-admin-screen/base-admin-screen.component.html index e4f858437..4f54ba8f4 100644 --- a/apps/red-ui/src/app/modules/admin/base-admin-screen/base-admin-screen.component.html +++ b/apps/red-ui/src/app/modules/admin/base-admin-screen/base-admin-screen.component.html @@ -1,26 +1,5 @@ - +
-
- - -
-
- - - - -
-
+ diff --git a/apps/red-ui/src/app/modules/admin/base-admin-screen/base-admin-screen.component.scss b/apps/red-ui/src/app/modules/admin/base-admin-screen/base-admin-screen.component.scss index e69de29bb..e7a72018d 100644 --- a/apps/red-ui/src/app/modules/admin/base-admin-screen/base-admin-screen.component.scss +++ b/apps/red-ui/src/app/modules/admin/base-admin-screen/base-admin-screen.component.scss @@ -0,0 +1,3 @@ +:host { + display: flex; +} diff --git a/apps/red-ui/src/app/modules/admin/base-admin-screen/base-admin-screen.component.ts b/apps/red-ui/src/app/modules/admin/base-admin-screen/base-admin-screen.component.ts index fd4af2801..f15acbbb4 100644 --- a/apps/red-ui/src/app/modules/admin/base-admin-screen/base-admin-screen.component.ts +++ b/apps/red-ui/src/app/modules/admin/base-admin-screen/base-admin-screen.component.ts @@ -1,7 +1,6 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; @Component({ - selector: 'redaction-base-admin-screen', templateUrl: './base-admin-screen.component.html', styleUrls: ['./base-admin-screen.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, diff --git a/apps/red-ui/src/app/modules/admin/base-dossier-templates-screen/base-dossier-template-screen.component.html b/apps/red-ui/src/app/modules/admin/base-dossier-templates-screen/base-dossier-template-screen.component.html new file mode 100644 index 000000000..ee08d19cc --- /dev/null +++ b/apps/red-ui/src/app/modules/admin/base-dossier-templates-screen/base-dossier-template-screen.component.html @@ -0,0 +1,26 @@ + + +
+ + +
+
+ + + + +
+
diff --git a/apps/red-ui/src/app/modules/admin/base-dossier-templates-screen/base-dossier-template-screen.component.ts b/apps/red-ui/src/app/modules/admin/base-dossier-templates-screen/base-dossier-template-screen.component.ts new file mode 100644 index 000000000..298ec017c --- /dev/null +++ b/apps/red-ui/src/app/modules/admin/base-dossier-templates-screen/base-dossier-template-screen.component.ts @@ -0,0 +1,7 @@ +import { ChangeDetectionStrategy, Component } from '@angular/core'; + +@Component({ + templateUrl: './base-dossier-template-screen.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class BaseDossierTemplateScreenComponent {} diff --git a/apps/red-ui/src/app/modules/admin/components/breadcrumbs/admin-breadcrumbs.component.ts b/apps/red-ui/src/app/modules/admin/components/breadcrumbs/admin-breadcrumbs.component.ts deleted file mode 100644 index 3c1d06a08..000000000 --- a/apps/red-ui/src/app/modules/admin/components/breadcrumbs/admin-breadcrumbs.component.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Component, Input } from '@angular/core'; -import { AppStateService } from '@state/app-state.service'; -import { UserPreferenceService } from '@services/user-preference.service'; -import { PermissionsService } from '@services/permissions.service'; -import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service'; - -@Component({ - selector: 'redaction-admin-breadcrumbs', - templateUrl: './admin-breadcrumbs.component.html', - styleUrls: ['./admin-breadcrumbs.component.scss'], -}) -export class AdminBreadcrumbsComponent { - @Input() root = false; - - constructor( - readonly userPreferenceService: UserPreferenceService, - readonly permissionService: PermissionsService, - readonly appStateService: AppStateService, - readonly dossierTemplatesService: DossierTemplatesService, - ) {} -} diff --git a/apps/red-ui/src/app/modules/admin/components/combo-chart/combo-chart.component.ts b/apps/red-ui/src/app/modules/admin/components/combo-chart/combo-chart.component.ts index fff29fbc4..0f949194c 100644 --- a/apps/red-ui/src/app/modules/admin/components/combo-chart/combo-chart.component.ts +++ b/apps/red-ui/src/app/modules/admin/components/combo-chart/combo-chart.component.ts @@ -12,7 +12,18 @@ import { import { curveLinear } from 'd3-shape'; import { scaleBand, scaleLinear, scalePoint, scaleTime } from 'd3-scale'; -import { BaseChartComponent, calculateViewDimensions, ColorHelper, LineSeriesComponent, ViewDimensions } from '@swimlane/ngx-charts'; +import { + BaseChartComponent, + calculateViewDimensions, + Color, + ColorHelper, + LegendPosition, + LineSeriesComponent, + Orientation, + ScaleType, + ViewDimensions, +} from '@swimlane/ngx-charts'; +import { ILineChartSeries } from './models'; @Component({ // eslint-disable-next-line @angular-eslint/component-selector @@ -25,7 +36,7 @@ export class ComboChartComponent extends BaseChartComponent { @Input() curve: any = curveLinear; @Input() legend = false; @Input() legendTitle = 'Legend'; - @Input() legendPosition = 'right'; + @Input() legendPosition: LegendPosition = LegendPosition.Right; @Input() xAxis; @Input() yAxis; @Input() showXAxisLabel; @@ -38,33 +49,33 @@ export class ComboChartComponent extends BaseChartComponent { @Input() gradient: boolean; @Input() showGridLines = true; @Input() activeEntries: any[] = []; - @Input() schemeType: string; + @Input() schemeType: ScaleType; @Input() xAxisTickFormatting: any; @Input() yAxisTickFormatting: any; @Input() yRightAxisTickFormatting: any; @Input() roundDomains = false; - @Input() colorSchemeLine: any; + @Input() colorSchemeLine: Color; @Input() autoScale; - @Input() lineChart: any; + @Input() lineChart: ILineChartSeries[]; @Input() yLeftAxisScaleFactor: any; @Input() yRightAxisScaleFactor: any; @Input() rangeFillOpacity: number; @Input() animations = true; @Input() noBarWhenZero = true; - @Output() activate: EventEmitter = new EventEmitter(); - @Output() deactivate: EventEmitter = new EventEmitter(); + @Output() activate = new EventEmitter<{ value; entries: unknown[] }>(); + @Output() deactivate = new EventEmitter<{ value; entries: unknown[] }>(); - @ContentChild('tooltipTemplate') tooltipTemplate: TemplateRef; - @ContentChild('seriesTooltipTemplate') seriesTooltipTemplate: TemplateRef; + @ContentChild('tooltipTemplate') tooltipTemplate: TemplateRef; + @ContentChild('seriesTooltipTemplate') seriesTooltipTemplate: TemplateRef; @ViewChild(LineSeriesComponent) lineSeriesComponent: LineSeriesComponent; dims: ViewDimensions; xScale: any; yScale: any; - xDomain: any; - yDomain: any; + xDomain: string[] | number[]; + yDomain: string[] | number[]; transform: string; colors: ColorHelper; colorsLine: ColorHelper; @@ -72,19 +83,19 @@ export class ComboChartComponent extends BaseChartComponent { xAxisHeight = 0; yAxisWidth = 0; legendOptions: any; - scaleType = 'linear'; + scaleType: ScaleType = ScaleType.Linear; xScaleLine; yScaleLine; xDomainLine; yDomainLine; seriesDomain; scaledAxis; - combinedSeries; + combinedSeries: ILineChartSeries[]; xSet; filteredDomain; hoveredVertical; - yOrientLeft = 'left'; - yOrientRight = 'right'; + yOrientLeft: Orientation = Orientation.Left; + yOrientRight: Orientation = Orientation.Right; legendSpacing = 0; bandwidth: number; barPadding = 8; @@ -176,15 +187,11 @@ export class ComboChartComponent extends BaseChartComponent { return this.combinedSeries.map(d => d.name); } - isDate(value): boolean { - if (value instanceof Date) { - return true; - } - - return false; + isDate(value): value is Date { + return value instanceof Date; } - getScaleType(values): string { + getScaleType(values): ScaleType { let date = true; let num = true; @@ -199,16 +206,16 @@ export class ComboChartComponent extends BaseChartComponent { } if (date) { - return 'time'; + return ScaleType.Time; } if (num) { - return 'linear'; + return ScaleType.Linear; } - return 'ordinal'; + return ScaleType.Ordinal; } getXDomainLine(): any[] { - let values = []; + let values: number[] = []; for (const results of this.lineChart) { for (const d of results.series) { @@ -239,7 +246,7 @@ export class ComboChartComponent extends BaseChartComponent { } getYDomainLine(): any[] { - const domain = []; + const domain: number[] = []; for (const results of this.lineChart) { for (const d of results.series) { @@ -263,7 +270,7 @@ export class ComboChartComponent extends BaseChartComponent { const max = Math.max(...domain); if (this.yRightAxisScaleFactor) { const minMax = this.yRightAxisScaleFactor(min, max); - return [Math.min(0, minMax.min), minMax.max]; + return [Math.min(0, minMax.min as number), minMax.max]; } else { min = Math.min(0, min); return [min, max]; @@ -317,12 +324,12 @@ export class ComboChartComponent extends BaseChartComponent { } getYDomain() { - const values = this.results.map(d => d.value); + const values: number[] = this.results.map(d => d.value); const min = Math.min(0, ...values); const max = Math.max(...values); if (this.yLeftAxisScaleFactor) { const minMax = this.yLeftAxisScaleFactor(min, max); - return [Math.min(0, minMax.min), minMax.max]; + return [Math.min(0, minMax.min as number), minMax.max]; } else { return [min, max]; } @@ -333,7 +340,7 @@ export class ComboChartComponent extends BaseChartComponent { } setColors(): void { - let domain; + let domain: number[] | string[]; if (this.schemeType === 'ordinal') { domain = this.xDomain; } else { diff --git a/apps/red-ui/src/app/modules/admin/components/combo-chart/combo-series-vertical.component.ts b/apps/red-ui/src/app/modules/admin/components/combo-chart/combo-series-vertical.component.ts index 6289f88d6..483125c2a 100644 --- a/apps/red-ui/src/app/modules/admin/components/combo-chart/combo-series-vertical.component.ts +++ b/apps/red-ui/src/app/modules/admin/components/combo-chart/combo-series-vertical.component.ts @@ -1,6 +1,6 @@ import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, Output } from '@angular/core'; import { animate, style, transition, trigger } from '@angular/animations'; -import { formatLabel } from '@swimlane/ngx-charts'; +import { Bar, BarOrientation, formatLabel, PlacementTypes, StyleTypes } from '@swimlane/ngx-charts'; @Component({ // eslint-disable-next-line @angular-eslint/component-selector @@ -17,7 +17,7 @@ import { formatLabel } from '@swimlane/ngx-charts'; [fill]="bar.color" [stops]="bar.gradientStops" [data]="bar.data" - [orientation]="'vertical'" + [orientation]="orientations.Vertical" [roundEdges]="bar.roundEdges" [gradient]="gradient" [isActive]="isActive(bar.data)" @@ -27,8 +27,8 @@ import { formatLabel } from '@swimlane/ngx-charts'; (deactivate)="deactivate.emit($event)" ngx-tooltip [tooltipDisabled]="tooltipDisabled" - [tooltipPlacement]="0" - [tooltipType]="1" + [tooltipPlacement]="tooltipPlacements.Top" + [tooltipType]="tooltipTypes.tooltip" [tooltipTitle]="bar.tooltipText" > `, @@ -67,6 +67,9 @@ export class ComboSeriesVerticalComponent implements OnChanges { bars: any; x: any; y: any; + readonly tooltipTypes = StyleTypes; + readonly tooltipPlacements = PlacementTypes; + readonly orientations = BarOrientation; ngOnChanges(): void { this.update(); @@ -91,7 +94,7 @@ export class ComboSeriesVerticalComponent implements OnChanges { const formattedLabel = formatLabel(label); const roundEdges = this.type === 'standard'; - const bar: any = { + const bar: Bar = { value, label, roundEdges, @@ -101,8 +104,15 @@ export class ComboSeriesVerticalComponent implements OnChanges { height: 0, x: 0, y: 0, + ariaLabel: label, + tooltipText: label, + color: undefined, + gradientStops: undefined, }; + let offset0 = d0; + let offset1 = offset0 + value; + if (this.type === 'standard') { bar.height = Math.abs(this.yScale(value) - this.yScale(0)); bar.x = this.xScale(label); @@ -113,18 +123,14 @@ export class ComboSeriesVerticalComponent implements OnChanges { bar.y = this.yScale(value); } } else if (this.type === 'stacked') { - const offset0 = d0; - const offset1 = offset0 + value; d0 += value; bar.height = this.yScale(offset0) - this.yScale(offset1); bar.x = 0; bar.y = this.yScale(offset1); - bar.offset0 = offset0; - bar.offset1 = offset1; + // bar.offset0 = offset0; + // bar.offset1 = offset1; } else if (this.type === 'normalized') { - let offset0 = d0; - let offset1 = offset0 + value; d0 += value; if (total > 0) { @@ -138,8 +144,8 @@ export class ComboSeriesVerticalComponent implements OnChanges { bar.height = this.yScale(offset0) - this.yScale(offset1); bar.x = 0; bar.y = this.yScale(offset1); - bar.offset0 = offset0; - bar.offset1 = offset1; + // bar.offset0 = offset0; + // bar.offset1 = offset1; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore value = (offset1 - offset0).toFixed(2) + '%'; @@ -152,8 +158,8 @@ export class ComboSeriesVerticalComponent implements OnChanges { bar.color = this.colors.getColor(value); bar.gradientStops = this.colors.getLinearGradientStops(value); } else { - bar.color = this.colors.getColor(bar.offset1); - bar.gradientStops = this.colors.getLinearGradientStops(bar.offset1, bar.offset0); + bar.color = this.colors.getColor(offset1); + bar.gradientStops = this.colors.getLinearGradientStops(offset1, offset0); } } diff --git a/apps/red-ui/src/app/modules/admin/components/combo-chart/models.ts b/apps/red-ui/src/app/modules/admin/components/combo-chart/models.ts new file mode 100644 index 000000000..7358f8e55 --- /dev/null +++ b/apps/red-ui/src/app/modules/admin/components/combo-chart/models.ts @@ -0,0 +1,11 @@ +export interface ISeries { + name: number; + value: number; + min: number; + max: number; +} + +export interface ILineChartSeries { + name: string; + series: ISeries[]; +} diff --git a/apps/red-ui/src/app/modules/admin/components/breadcrumbs/admin-breadcrumbs.component.html b/apps/red-ui/src/app/modules/admin/components/dossier-template-breadcrumbs/dossier-template-breadcrumbs.component.html similarity index 69% rename from apps/red-ui/src/app/modules/admin/components/breadcrumbs/admin-breadcrumbs.component.html rename to apps/red-ui/src/app/modules/admin/components/dossier-template-breadcrumbs/dossier-template-breadcrumbs.component.html index 1afff1dac..b91797330 100644 --- a/apps/red-ui/src/app/modules/admin/components/breadcrumbs/admin-breadcrumbs.component.html +++ b/apps/red-ui/src/app/modules/admin/components/dossier-template-breadcrumbs/dossier-template-breadcrumbs.component.html @@ -3,13 +3,14 @@ *ngIf="root || !!dossierTemplatesService.activeDossierTemplate" [routerLink]="'/main/admin/dossier-templates'" class="breadcrumb" - translate="dossier-templates" + translate="dossier-templates.label" >
- - - - {{ activeDossierTemplate.name }} + + + + + {{ dossierTemplate.name }} @@ -17,7 +18,7 @@ ; + + constructor( + readonly userPreferenceService: UserPreferenceService, + readonly permissionService: PermissionsService, + readonly appStateService: AppStateService, + readonly dossierTemplatesService: DossierTemplatesService, + private readonly _route: ActivatedRoute, + ) { + this.dossierTemplate$ = _route.paramMap.pipe( + map(params => params.get('dossierTemplateId')), + switchMap((dossierTemplateId: string) => this.dossierTemplatesService.getEntityChanged$(dossierTemplateId)), + ); + } +} diff --git a/apps/red-ui/src/app/modules/admin/components/users-stats/users-stats.component.scss b/apps/red-ui/src/app/modules/admin/components/users-stats/users-stats.component.scss index 11153dd15..baf2e798b 100644 --- a/apps/red-ui/src/app/modules/admin/components/users-stats/users-stats.component.scss +++ b/apps/red-ui/src/app/modules/admin/components/users-stats/users-stats.component.scss @@ -13,7 +13,3 @@ left: 270px; } } - -.mt-44 { - margin-top: 44px; -} diff --git a/apps/red-ui/src/app/modules/admin/dialogs/add-edit-dictionary-dialog/add-edit-dictionary-dialog.component.html b/apps/red-ui/src/app/modules/admin/dialogs/add-edit-dictionary-dialog/add-edit-dictionary-dialog.component.html index 2e69716c7..7acf0cf23 100644 --- a/apps/red-ui/src/app/modules/admin/dialogs/add-edit-dictionary-dialog/add-edit-dictionary-dialog.component.html +++ b/apps/red-ui/src/app/modules/admin/dialogs/add-edit-dictionary-dialog/add-edit-dictionary-dialog.component.html @@ -95,11 +95,11 @@
-
- + diff --git a/apps/red-ui/src/app/modules/admin/dialogs/add-edit-dictionary-dialog/add-edit-dictionary-dialog.component.scss b/apps/red-ui/src/app/modules/admin/dialogs/add-edit-dictionary-dialog/add-edit-dictionary-dialog.component.scss index d630c483b..b057ad746 100644 --- a/apps/red-ui/src/app/modules/admin/dialogs/add-edit-dictionary-dialog/add-edit-dictionary-dialog.component.scss +++ b/apps/red-ui/src/app/modules/admin/dialogs/add-edit-dictionary-dialog/add-edit-dictionary-dialog.component.scss @@ -10,10 +10,6 @@ } } -.mb-14 { - margin-bottom: 14px; -} - .technical-name { font-weight: 600; } 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 737d96915..3e1ec0ec1 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 @@ -1,12 +1,12 @@ -import { ChangeDetectionStrategy, Component, Inject } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Inject, Injector } from '@angular/core'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; -import { Observable } from 'rxjs'; +import { firstValueFrom, Observable } from 'rxjs'; import { BaseDialogComponent, shareDistinctLast, Toaster } from '@iqser/common-ui'; import { TranslateService } from '@ngx-translate/core'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { AppStateService } from '@state/app-state.service'; -import { toKebabCase } from '@utils/functions'; +import { toSnakeCase } from '@utils/functions'; import { DictionaryService } from '@shared/services/dictionary.service'; import { Dictionary, IDictionary } from '@red/domain'; import { UserService } from '@services/user.service'; @@ -21,7 +21,6 @@ import { HttpStatusCode } from '@angular/common/http'; }) export class AddEditDictionaryDialogComponent extends BaseDialogComponent { readonly dictionary = this._data.dictionary; - readonly form: FormGroup = this._getForm(this.dictionary); readonly canEditLabel$ = this._canEditLabel$; readonly technicalName$: Observable; readonly dialogHeader = this._translateService.instant('add-edit-dictionary.title', { @@ -29,7 +28,6 @@ export class AddEditDictionaryDialogComponent extends BaseDialogComponent { name: this._data.dictionary?.label, }); readonly hasColor$: Observable; - readonly disabled = false; private readonly _dossierTemplateId = this._data.dossierTemplateId; constructor( @@ -39,37 +37,18 @@ export class AddEditDictionaryDialogComponent extends BaseDialogComponent { private readonly _appStateService: AppStateService, private readonly _translateService: TranslateService, private readonly _dictionaryService: DictionaryService, - private readonly _dialogRef: MatDialogRef, + protected readonly _injector: Injector, + protected readonly _dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) private readonly _data: { readonly dictionary: Dictionary; readonly dossierTemplateId: string }, ) { - super(); + super(_injector, _dialogRef); + this.form = this._getForm(this.dictionary); + this.initialFormValue = this.form.getRawValue(); this.hasColor$ = this._colorEmpty$; this.technicalName$ = this.form.get('label').valueChanges.pipe(map(value => this._toTechnicalName(value))); } - get valid(): boolean { - return this.form.valid; - } - - get changed(): boolean { - if (!this.dictionary) { - return true; - } - - for (const key of Object.keys(this.form.getRawValue())) { - if (key === 'caseSensitive') { - if (this.getDictCaseSensitive(this.dictionary) !== this.form.get(key).value) { - return true; - } - } else if (this.dictionary[key] !== this.form.get(key).value) { - return true; - } - } - - return false; - } - private get _canEditLabel$() { return this.userService.currentUser$.pipe( map(user => user.isAdmin || !this._data.dictionary), @@ -98,8 +77,7 @@ export class AddEditDictionaryDialogComponent extends BaseDialogComponent { observable = this._dictionaryService.addDictionary({ ...dictionary, dossierTemplateId }); } - return observable - .toPromise() + return firstValueFrom(observable) .then(() => this._dialogRef.close(true)) .catch(error => { if (error.status === HttpStatusCode.Conflict) { @@ -126,11 +104,11 @@ export class AddEditDictionaryDialogComponent extends BaseDialogComponent { private _toTechnicalName(value: string) { const existingTechnicalNames = Object.keys(this._appStateService.dictionaryData[this._dossierTemplateId]); - const baseTechnicalName = toKebabCase(value.trim()); + const baseTechnicalName = toSnakeCase(value.trim()); let technicalName = baseTechnicalName; let suffix = 1; while (existingTechnicalNames.includes(technicalName)) { - technicalName = [baseTechnicalName, suffix++].join('-'); + technicalName = [baseTechnicalName, suffix++].join('_'); } return technicalName; } diff --git a/apps/red-ui/src/app/modules/admin/dialogs/add-edit-dossier-attribute-dialog/add-edit-dossier-attribute-dialog.component.html b/apps/red-ui/src/app/modules/admin/dialogs/add-edit-dossier-attribute-dialog/add-edit-dossier-attribute-dialog.component.html index ab55a854a..4abe7268e 100644 --- a/apps/red-ui/src/app/modules/admin/dialogs/add-edit-dossier-attribute-dialog/add-edit-dossier-attribute-dialog.component.html +++ b/apps/red-ui/src/app/modules/admin/dialogs/add-edit-dossier-attribute-dialog/add-edit-dossier-attribute-dialog.component.html @@ -35,11 +35,11 @@
-
- + 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 96642be2e..f20fa7042 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,8 +1,8 @@ -import { Component, HostListener, Inject, OnDestroy } from '@angular/core'; +import { Component, HostListener, Inject, Injector, OnDestroy } from '@angular/core'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { DossierAttributeConfigTypes, FileAttributeConfigTypes, IDossierAttributeConfig } from '@red/domain'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; -import { AutoUnsubscribe, IqserEventTarget, LoadingService, Toaster } from '@iqser/common-ui'; +import { BaseDialogComponent, IqserEventTarget, LoadingService, Toaster } from '@iqser/common-ui'; import { HttpErrorResponse } from '@angular/common/http'; import { DossierAttributesService } from '@shared/services/controller-wrappers/dossier-attributes.service'; import { dossierAttributeTypesTranslations } from '../../translations/dossier-attribute-types-translations'; @@ -12,9 +12,8 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; templateUrl: './add-edit-dossier-attribute-dialog.component.html', styleUrls: ['./add-edit-dossier-attribute-dialog.component.scss'], }) -export class AddEditDossierAttributeDialogComponent extends AutoUnsubscribe implements OnDestroy { +export class AddEditDossierAttributeDialogComponent extends BaseDialogComponent implements OnDestroy { dossierAttribute: IDossierAttributeConfig = this.data.dossierAttribute; - readonly form: FormGroup = this._getForm(this.dossierAttribute); readonly translations = dossierAttributeTypesTranslations; readonly typeOptions = Object.keys(DossierAttributeConfigTypes); @@ -23,11 +22,14 @@ export class AddEditDossierAttributeDialogComponent extends AutoUnsubscribe impl private readonly _loadingService: LoadingService, private readonly _dossierAttributesService: DossierAttributesService, private readonly _toaster: Toaster, - readonly dialogRef: MatDialogRef, + protected readonly _injector: Injector, + protected readonly _dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) readonly data: { readonly dossierAttribute: IDossierAttributeConfig }, ) { - super(); + super(_injector, _dialogRef); + this.form = this._getForm(this.dossierAttribute); + this.initialFormValue = this.form.getRawValue(); } get changed(): boolean { @@ -55,7 +57,7 @@ export class AddEditDossierAttributeDialogComponent extends AutoUnsubscribe impl this._dossierAttributesService.createOrUpdate(attribute).subscribe( () => { - this.dialogRef.close(true); + this._dialogRef.close(true); }, (error: HttpErrorResponse) => { this._loadingService.stop(); diff --git a/apps/red-ui/src/app/modules/admin/dialogs/add-edit-dossier-template-dialog/add-edit-dossier-template-dialog.component.html b/apps/red-ui/src/app/modules/admin/dialogs/add-edit-dossier-template-dialog/add-edit-dossier-template-dialog.component.html index a8ee5cdf2..a5d11d920 100644 --- a/apps/red-ui/src/app/modules/admin/dialogs/add-edit-dossier-template-dialog/add-edit-dossier-template-dialog.component.html +++ b/apps/red-ui/src/app/modules/admin/dialogs/add-edit-dossier-template-dialog/add-edit-dossier-template-dialog.component.html @@ -33,16 +33,11 @@
- + {{ 'add-edit-dossier-template.form.valid-from' | translate }} - + {{ 'add-edit-dossier-template.form.valid-to' | translate }}
@@ -87,11 +82,11 @@
-
- + diff --git a/apps/red-ui/src/app/modules/admin/dialogs/add-edit-dossier-template-dialog/add-edit-dossier-template-dialog.component.ts b/apps/red-ui/src/app/modules/admin/dialogs/add-edit-dossier-template-dialog/add-edit-dossier-template-dialog.component.ts index 39025d390..778150cc5 100644 --- a/apps/red-ui/src/app/modules/admin/dialogs/add-edit-dossier-template-dialog/add-edit-dossier-template-dialog.component.ts +++ b/apps/red-ui/src/app/modules/admin/dialogs/add-edit-dossier-template-dialog/add-edit-dossier-template-dialog.component.ts @@ -1,23 +1,23 @@ -import { Component, Inject } from '@angular/core'; +import { Component, Inject, Injector } from '@angular/core'; import { AppStateService } from '@state/app-state.service'; -import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { AbstractControl, FormBuilder, FormGroup, Validators } from '@angular/forms'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import * as moment from 'moment'; import { Moment } from 'moment'; import { applyIntervalConstraints } from '@utils/date-inputs-utils'; import { downloadTypesTranslations } from '../../../../translations/download-types-translations'; import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service'; -import { BaseDialogComponent, Toaster } from '@iqser/common-ui'; +import { BaseDialogComponent, LoadingService, Toaster } from '@iqser/common-ui'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { DownloadFileType, IDossierTemplate } from '@red/domain'; import { HttpStatusCode } from '@angular/common/http'; +import { firstValueFrom } from 'rxjs'; @Component({ templateUrl: './add-edit-dossier-template-dialog.component.html', styleUrls: ['./add-edit-dossier-template-dialog.component.scss'], }) export class AddEditDossierTemplateDialogComponent extends BaseDialogComponent { - readonly form: FormGroup = this._getForm(); hasValidFrom: boolean; hasValidTo: boolean; downloadTypesEnum: DownloadFileType[] = ['ORIGINAL', 'PREVIEW', 'REDACTED']; @@ -25,79 +25,64 @@ export class AddEditDossierTemplateDialogComponent extends BaseDialogComponent { key: type, label: downloadTypesTranslations[type], })); - readonly disabled = false; private _previousValidFrom: Moment; private _previousValidTo: Moment; + private _lastValidFrom: Moment; + private _lastValidTo: Moment; constructor( private readonly _appStateService: AppStateService, private readonly _toaster: Toaster, private readonly _dossierTemplatesService: DossierTemplatesService, private readonly _formBuilder: FormBuilder, - public dialogRef: MatDialogRef, + protected readonly _injector: Injector, + protected readonly _dialogRef: MatDialogRef, + private readonly _loadingService: LoadingService, @Inject(MAT_DIALOG_DATA) readonly dossierTemplate: IDossierTemplate, ) { - super(); + super(_injector, _dialogRef); + this.form = this._getForm(); + this.initialFormValue = this.form.getRawValue(); this.hasValidFrom = !!this.dossierTemplate?.validFrom; this.hasValidTo = !!this.dossierTemplate?.validTo; - this._previousValidFrom = this.form.get('validFrom').value; - this._previousValidTo = this.form.get('validTo').value; + this._previousValidFrom = this._lastValidFrom = this.form.get('validFrom').value; + this._previousValidTo = this._lastValidTo = this.form.get('validTo').value; - this.form.valueChanges.subscribe(value => { + this.addSubscription = this.form.valueChanges.subscribe(value => { this._applyValidityIntervalConstraints(value); }); + + this.addSubscription = this.form.controls['validFrom'].valueChanges.subscribe(value => { + this._lastValidFrom = value ? value : this._lastValidFrom; + }); + this.addSubscription = this.form.controls['validTo'].valueChanges.subscribe(value => { + this._lastValidFrom = value ? value : this._lastValidFrom; + }); } - get valid(): boolean { - return this.form.valid; - } - - get changed(): boolean { - if (!this.dossierTemplate) { - return true; + toggleHasValid(extremity: string) { + if (extremity === 'from') { + this.hasValidFrom = !this.hasValidFrom; + this.form.controls['validFrom'].setValue(this.hasValidFrom ? this._lastValidFrom : null); + } else { + this.hasValidTo = !this.hasValidTo; + this.form.controls['validTo'].setValue(this.hasValidTo ? this._lastValidTo : null); } - - for (const key of Object.keys(this.form.getRawValue())) { - const formValue = this.form.get(key).value; - const objectValue = this.dossierTemplate[key]; - if (key === 'validFrom') { - if (this.hasValidFrom !== !!objectValue || (this.hasValidFrom && !moment(objectValue).isSame(moment(formValue)))) { - return true; - } - } else if (key === 'validTo') { - if (this.hasValidTo !== !!objectValue || (this.hasValidTo && !moment(objectValue).isSame(moment(formValue)))) { - return true; - } - } else if (formValue instanceof Array) { - if (objectValue.length !== formValue.length) { - return true; - } - for (const item of objectValue) { - if (!formValue.includes(item)) { - return true; - } - } - } else if (objectValue !== formValue) { - return true; - } - } - - return false; } async save() { + this._loadingService.start(); try { const dossierTemplate = { dossierTemplateId: this.dossierTemplate?.dossierTemplateId, ...this.form.getRawValue(), validFrom: this.hasValidFrom ? this.form.get('validFrom').value : null, validTo: this.hasValidTo ? this.form.get('validTo').value : null, - }; + } as IDossierTemplate; await this._dossierTemplatesService.createOrUpdate(dossierTemplate).toPromise(); - await this._dossierTemplatesService.loadAll().toPromise(); await this._appStateService.loadDictionaryData(); - this.dialogRef.close(true); + this._dialogRef.close(true); } catch (error: any) { const message = error.status === HttpStatusCode.Conflict @@ -105,6 +90,7 @@ export class AddEditDossierTemplateDialogComponent extends BaseDialogComponent { : _('add-edit-dossier-template.error.generic'); this._toaster.error(message, { error }); } + this._loadingService.stop(); } private _getForm(): FormGroup { @@ -134,7 +120,7 @@ export class AddEditDossierTemplateDialogComponent extends BaseDialogComponent { } private _requiredIfValidator(predicate) { - return formControl => { + return (formControl: AbstractControl) => { if (!formControl.parent) { return null; } diff --git a/apps/red-ui/src/app/modules/admin/dialogs/add-edit-file-attribute-dialog/add-edit-file-attribute-dialog.component.html b/apps/red-ui/src/app/modules/admin/dialogs/add-edit-file-attribute-dialog/add-edit-file-attribute-dialog.component.html index 4715598f6..7867b41a6 100644 --- a/apps/red-ui/src/app/modules/admin/dialogs/add-edit-file-attribute-dialog/add-edit-file-attribute-dialog.component.html +++ b/apps/red-ui/src/app/modules/admin/dialogs/add-edit-file-attribute-dialog/add-edit-file-attribute-dialog.component.html @@ -84,11 +84,11 @@
-
- + diff --git a/apps/red-ui/src/app/modules/admin/dialogs/add-edit-file-attribute-dialog/add-edit-file-attribute-dialog.component.ts b/apps/red-ui/src/app/modules/admin/dialogs/add-edit-file-attribute-dialog/add-edit-file-attribute-dialog.component.ts index 7346bf384..413cdc418 100644 --- a/apps/red-ui/src/app/modules/admin/dialogs/add-edit-file-attribute-dialog/add-edit-file-attribute-dialog.component.ts +++ b/apps/red-ui/src/app/modules/admin/dialogs/add-edit-file-attribute-dialog/add-edit-file-attribute-dialog.component.ts @@ -1,10 +1,10 @@ -import { ChangeDetectionStrategy, Component, Inject } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Inject, Injector } from '@angular/core'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { FileAttributeConfigTypes, IFileAttributeConfig } from '@red/domain'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { fileAttributeTypesTranslations } from '../../translations/file-attribute-types-translations'; import { FileAttributesService } from '@services/entity-services/file-attributes.service'; -import { BaseDialogComponent } from '@iqser/common-ui'; +import { BaseDialogComponent } from '../../../../../../../../libs/common-ui/src'; @Component({ selector: 'redaction-add-edit-file-attribute-dialog', @@ -13,12 +13,10 @@ import { BaseDialogComponent } from '@iqser/common-ui'; changeDetection: ChangeDetectionStrategy.OnPush, }) export class AddEditFileAttributeDialogComponent extends BaseDialogComponent { - readonly disabled = false; DISPLAYED_FILTERABLE_LIMIT = 3; translations = fileAttributeTypesTranslations; fileAttribute: IFileAttributeConfig = this.data.fileAttribute; dossierTemplateId: string = this.data.dossierTemplateId; - readonly form!: FormGroup; readonly typeOptions = Object.keys(FileAttributeConfigTypes); readonly canSetDisplayed!: boolean; readonly canSetFilterable!: boolean; @@ -26,7 +24,8 @@ export class AddEditFileAttributeDialogComponent extends BaseDialogComponent { constructor( private readonly _formBuilder: FormBuilder, private readonly _fileAttributesService: FileAttributesService, - public dialogRef: MatDialogRef, + protected readonly _injector: Injector, + protected readonly _dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data: { fileAttribute: IFileAttributeConfig; @@ -35,32 +34,11 @@ export class AddEditFileAttributeDialogComponent extends BaseDialogComponent { numberOfFilterableAttrs: number; }, ) { - super(); + super(_injector, _dialogRef); this.canSetDisplayed = data.numberOfDisplayedAttrs < this.DISPLAYED_FILTERABLE_LIMIT || data.fileAttribute?.displayedInFileList; this.canSetFilterable = data.numberOfFilterableAttrs < this.DISPLAYED_FILTERABLE_LIMIT || data.fileAttribute?.filterable; this.form = this._getForm(this.fileAttribute); - } - - get valid(): boolean { - return this.form.valid; - } - - get changed(): boolean { - if (!this.fileAttribute) { - return true; - } - - for (const key of Object.keys(this.form.getRawValue())) { - if (key === 'readonly') { - if (this.fileAttribute.editable === this.form.get(key).value) { - return true; - } - } else if (this.fileAttribute[key] !== this.form.get(key).value) { - return true; - } - } - - return false; + this.initialFormValue = this.form.getRawValue(); } save() { @@ -69,7 +47,7 @@ export class AddEditFileAttributeDialogComponent extends BaseDialogComponent { editable: !this.form.get('readonly').value, ...this.form.getRawValue(), }; - this.dialogRef.close(fileAttribute); + this._dialogRef.close(fileAttribute); } private _getForm(fileAttribute: IFileAttributeConfig): FormGroup { diff --git a/apps/red-ui/src/app/modules/admin/dialogs/add-edit-user-dialog/add-edit-user-dialog.component.html b/apps/red-ui/src/app/modules/admin/dialogs/add-edit-user-dialog/add-edit-user-dialog.component.html index 1f17a0d25..51cae0fa7 100644 --- a/apps/red-ui/src/app/modules/admin/dialogs/add-edit-user-dialog/add-edit-user-dialog.component.html +++ b/apps/red-ui/src/app/modules/admin/dialogs/add-edit-user-dialog/add-edit-user-dialog.component.html @@ -1,17 +1,18 @@
- +
diff --git a/apps/red-ui/src/app/modules/admin/dialogs/add-edit-user-dialog/add-edit-user-dialog.component.ts b/apps/red-ui/src/app/modules/admin/dialogs/add-edit-user-dialog/add-edit-user-dialog.component.ts index 30b9e7c21..a35e42d2e 100644 --- a/apps/red-ui/src/app/modules/admin/dialogs/add-edit-user-dialog/add-edit-user-dialog.component.ts +++ b/apps/red-ui/src/app/modules/admin/dialogs/add-edit-user-dialog/add-edit-user-dialog.component.ts @@ -1,18 +1,44 @@ -import { Component, Inject } from '@angular/core'; +import { Component, Inject, Injector, ViewChild } from '@angular/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { User } from '@red/domain'; +import { UserDetailsComponent } from './user-details/user-details.component'; +import { BaseDialogComponent } from '@iqser/common-ui'; @Component({ selector: 'redaction-add-edit-user-dialog', templateUrl: './add-edit-user-dialog.component.html', styleUrls: ['./add-edit-user-dialog.component.scss'], }) -export class AddEditUserDialogComponent { +export class AddEditUserDialogComponent extends BaseDialogComponent { + @ViewChild(UserDetailsComponent) private readonly _userDetailsComponent: UserDetailsComponent; + resettingPassword = false; - constructor(readonly dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) readonly user: User) {} + constructor( + protected readonly _injector: Injector, + protected readonly _dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) readonly user: User, + ) { + super(_injector, _dialogRef); + } toggleResetPassword() { this.resettingPassword = !this.resettingPassword; } + + async save(): Promise { + await this._userDetailsComponent.save(); + } + + get changed(): boolean { + return this._userDetailsComponent.changed; + } + + get valid(): boolean { + return this._userDetailsComponent.valid; + } + + closeDialog(event) { + this._dialogRef.close(event); + } } diff --git a/apps/red-ui/src/app/modules/admin/dialogs/add-edit-user-dialog/reset-password/reset-password.component.ts b/apps/red-ui/src/app/modules/admin/dialogs/add-edit-user-dialog/reset-password/reset-password.component.ts index 1839e3bfe..441249ef4 100644 --- a/apps/red-ui/src/app/modules/admin/dialogs/add-edit-user-dialog/reset-password/reset-password.component.ts +++ b/apps/red-ui/src/app/modules/admin/dialogs/add-edit-user-dialog/reset-password/reset-password.component.ts @@ -4,6 +4,7 @@ import { UserService } from '@services/user.service'; import { LoadingService, Toaster } from '@iqser/common-ui'; import { User } from '@red/domain'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; +import { firstValueFrom } from 'rxjs'; @Component({ selector: 'redaction-reset-password', @@ -25,15 +26,15 @@ export class ResetPasswordComponent { async save() { this._loadingService.start(); try { - await this._userService - .resetPassword( + await firstValueFrom( + this._userService.resetPassword( { password: this.form.get('temporaryPassword').value, temporary: true, }, this.user.id, - ) - .toPromise(); + ), + ); this.toggleResetPassword.emit(); } catch (error) { this._toaster.error(_('reset-password-dialog.error.password-policy')); diff --git a/apps/red-ui/src/app/modules/admin/dialogs/add-edit-user-dialog/user-details/user-details.component.html b/apps/red-ui/src/app/modules/admin/dialogs/add-edit-user-dialog/user-details/user-details.component.html index 75615f9ec..4ca949f7f 100644 --- a/apps/red-ui/src/app/modules/admin/dialogs/add-edit-user-dialog/user-details/user-details.component.html +++ b/apps/red-ui/src/app/modules/admin/dialogs/add-edit-user-dialog/user-details/user-details.component.html @@ -53,6 +53,6 @@ icon="iqser:trash" > -
+
diff --git a/apps/red-ui/src/app/modules/admin/dialogs/add-edit-user-dialog/user-details/user-details.component.ts b/apps/red-ui/src/app/modules/admin/dialogs/add-edit-user-dialog/user-details/user-details.component.ts index ec82eb9d5..220f6aa12 100644 --- a/apps/red-ui/src/app/modules/admin/dialogs/add-edit-user-dialog/user-details/user-details.component.ts +++ b/apps/red-ui/src/app/modules/admin/dialogs/add-edit-user-dialog/user-details/user-details.component.ts @@ -1,28 +1,29 @@ import { Component, EventEmitter, Input, OnChanges, OnDestroy, Output } from '@angular/core'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { AdminDialogService } from '../../../services/admin-dialog.service'; -import { AutoUnsubscribe, IconButtonTypes, LoadingService, Toaster } from '@iqser/common-ui'; +import { BaseFormComponent, IconButtonTypes, LoadingService, Toaster } from '@iqser/common-ui'; import { rolesTranslations } from '../../../../../translations/roles-translations'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { User } from '@red/domain'; import { UserService } from '@services/user.service'; import { HttpStatusCode } from '@angular/common/http'; +import { firstValueFrom } from 'rxjs'; @Component({ selector: 'redaction-user-details', templateUrl: './user-details.component.html', styleUrls: ['./user-details.component.scss'], }) -export class UserDetailsComponent extends AutoUnsubscribe implements OnChanges, OnDestroy { +export class UserDetailsComponent extends BaseFormComponent implements OnChanges, OnDestroy { readonly iconButtonTypes = IconButtonTypes; @Input() user: User; @Output() readonly toggleResetPassword = new EventEmitter(); @Output() readonly closeDialog = new EventEmitter(); + @Output() readonly cancel = new EventEmitter(); readonly ROLES = ['RED_USER', 'RED_MANAGER', 'RED_USER_ADMIN', 'RED_ADMIN']; readonly translations = rolesTranslations; - form: FormGroup; private readonly _ROLE_REQUIREMENTS = { RED_MANAGER: 'RED_USER', RED_ADMIN: 'RED_USER_ADMIN' }; constructor( @@ -35,29 +36,6 @@ export class UserDetailsComponent extends AutoUnsubscribe implements OnChanges, super(); } - get changed(): boolean { - if (!this.user) { - return true; - } - - if (this.user.roles.length !== this.activeRoles.length) { - return true; - } - - for (const key of Object.keys(this.form.getRawValue())) { - const keyValue = this.form.get(key).value; - if (key.startsWith('RED_')) { - if (this.user.roles.includes(key) !== keyValue) { - return true; - } - } else if (this.user[key] !== keyValue) { - return true; - } - } - - return false; - } - get activeRoles(): string[] { return this.ROLES.reduce((acc, role) => { if (this.form.get(role).value) { @@ -85,6 +63,7 @@ export class UserDetailsComponent extends AutoUnsubscribe implements OnChanges, ngOnChanges() { this.form = this._getForm(); this._setRolesRequirements(); + this.initialFormValue = this.form.getRawValue(); } shouldBeDisabled(role: string): boolean { @@ -107,9 +86,7 @@ export class UserDetailsComponent extends AutoUnsubscribe implements OnChanges, const userData = { ...this.form.getRawValue(), roles: this.activeRoles }; if (!this.user) { - await this.userService - .create(userData) - .toPromise() + await firstValueFrom(this.userService.create(userData)) .then(() => { this.closeDialog.emit(true); }) @@ -122,7 +99,7 @@ export class UserDetailsComponent extends AutoUnsubscribe implements OnChanges, this._loadingService.stop(); }); } else { - await this.userService.updateProfile(userData, this.user.id).toPromise(); + await firstValueFrom(this.userService.updateProfile(userData, this.user.id)); this.closeDialog.emit(true); } } @@ -140,7 +117,7 @@ export class UserDetailsComponent extends AutoUnsubscribe implements OnChanges, email: [ { value: this.user?.email, - disabled: !!this.user, + disabled: !!this.user?.email, }, [Validators.required, Validators.email], ], diff --git a/apps/red-ui/src/app/modules/admin/dialogs/confirm-delete-file-attribute-dialog/confirm-delete-file-attribute-dialog.component.html b/apps/red-ui/src/app/modules/admin/dialogs/confirm-delete-attribute-dialog/confirm-delete-attribute-dialog.component.html similarity index 64% rename from apps/red-ui/src/app/modules/admin/dialogs/confirm-delete-file-attribute-dialog/confirm-delete-file-attribute-dialog.component.html rename to apps/red-ui/src/app/modules/admin/dialogs/confirm-delete-attribute-dialog/confirm-delete-attribute-dialog.component.html index d90c5f201..b7442e49a 100644 --- a/apps/red-ui/src/app/modules/admin/dialogs/confirm-delete-file-attribute-dialog/confirm-delete-file-attribute-dialog.component.html +++ b/apps/red-ui/src/app/modules/admin/dialogs/confirm-delete-attribute-dialog/confirm-delete-attribute-dialog.component.html @@ -1,13 +1,6 @@
- {{ - 'confirm-delete-file-attribute.title' - | translate - : { - type: type, - name: fileAttribute?.label - } - }} + {{ 'confirm-delete-file-attribute.title' | translate: translateArgs }}
@@ -20,14 +13,11 @@
- - {{ checkbox.label | translate: { type: type } }} - + + + {{ checkbox.label }} + +
@@ -35,7 +25,7 @@ {{ 'confirm-delete-file-attribute.delete' | translate: { type: type } }}
+ value instanceof FileAttributeConfig; + +interface CheckBox { + value: boolean; + label: string; +} + +interface DialogData { + attribute: FileAttributeConfig | DossierAttributeConfig; + count: number; +} + +@Component({ + selector: 'redaction-confirm-delete-attribute-dialog', + templateUrl: './confirm-delete-attribute-dialog.component.html', + styleUrls: ['./confirm-delete-attribute-dialog.component.scss'], +}) +export class ConfirmDeleteAttributeDialogComponent { + checkboxes: CheckBox[]; + showToast = false; + + constructor( + private readonly _translateService: TranslateService, + readonly dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: DialogData, + ) { + this.checkboxes = this.checkBoxConfig; + } + + get checkBoxConfig(): CheckBox[] { + const checkBoxes = isFileAttributeConfig(this.data.attribute) ? this._fileAttributeCheckboxes : this._dossierAttributeCheckboxes; + if (this.data.count !== 0) { + checkBoxes.push({ + value: false, + label: this._translateService.instant('confirm-delete-file-attribute.impacted-report', { count: this.data.count }), + }); + } + + return checkBoxes; + } + + get valid() { + return this.checkboxes.reduce((acc, currentValue) => acc && currentValue.value, true); + } + + get type(): 'bulk' | 'single' { + return this.data.attribute ? 'single' : 'bulk'; + } + + get translateArgs() { + return { + type: this.type, + name: this.data.attribute?.label, + }; + } + + private get _fileAttributeCheckboxes(): CheckBox[] { + return [ + { + value: false, + label: this._translateService.instant('confirm-delete-file-attribute.file-impacted-documents', { type: this.type }), + }, + { value: false, label: this._translateService.instant('confirm-delete-file-attribute.file-lost-details') }, + ]; + } + + private get _dossierAttributeCheckboxes(): CheckBox[] { + return [ + { value: false, label: this._translateService.instant('confirm-delete-file-attribute.dossier-impacted-documents') }, + { value: false, label: this._translateService.instant('confirm-delete-file-attribute.dossier-lost-details') }, + ]; + } + + deleteFileAttribute() { + if (this.valid) { + this.dialogRef.close(true); + } else { + this.showToast = true; + } + } +} diff --git a/apps/red-ui/src/app/modules/admin/dialogs/confirm-delete-file-attribute-dialog/confirm-delete-file-attribute-dialog.component.ts b/apps/red-ui/src/app/modules/admin/dialogs/confirm-delete-file-attribute-dialog/confirm-delete-file-attribute-dialog.component.ts deleted file mode 100644 index 1cf2cee1b..000000000 --- a/apps/red-ui/src/app/modules/admin/dialogs/confirm-delete-file-attribute-dialog/confirm-delete-file-attribute-dialog.component.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { Component, Inject } from '@angular/core'; -import { IFileAttributeConfig } from '@red/domain'; -import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; -import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; - -@Component({ - selector: 'redaction-confirm-delete-file-attribute-dialog', - templateUrl: './confirm-delete-file-attribute-dialog.component.html', - styleUrls: ['./confirm-delete-file-attribute-dialog.component.scss'], -}) -export class ConfirmDeleteFileAttributeDialogComponent { - fileAttribute: IFileAttributeConfig; - checkboxes = [ - { value: false, label: _('confirm-delete-file-attribute.impacted-documents') }, - { value: false, label: _('confirm-delete-file-attribute.lost-details') }, - ]; - showToast = false; - - constructor( - public dialogRef: MatDialogRef, - @Inject(MAT_DIALOG_DATA) public data: IFileAttributeConfig, - ) { - this.fileAttribute = data; - } - - get valid() { - return this.checkboxes[0].value && this.checkboxes[1].value; - } - - get type(): 'bulk' | 'single' { - return this.fileAttribute ? 'single' : 'bulk'; - } - - deleteFileAttribute() { - if (this.valid) { - this.dialogRef.close(true); - } else { - this.showToast = true; - } - } - - cancel() { - this.dialogRef.close(); - } -} diff --git a/apps/red-ui/src/app/modules/admin/dialogs/confirm-delete-users-dialog/confirm-delete-users-dialog.component.ts b/apps/red-ui/src/app/modules/admin/dialogs/confirm-delete-users-dialog/confirm-delete-users-dialog.component.ts index b437e57ec..62cf4cb8b 100644 --- a/apps/red-ui/src/app/modules/admin/dialogs/confirm-delete-users-dialog/confirm-delete-users-dialog.component.ts +++ b/apps/red-ui/src/app/modules/admin/dialogs/confirm-delete-users-dialog/confirm-delete-users-dialog.component.ts @@ -4,6 +4,7 @@ import { List, LoadingService } from '@iqser/common-ui'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { DossiersService } from '@services/entity-services/dossiers.service'; import { UserService } from '@services/user.service'; +import { firstValueFrom } from 'rxjs'; @Component({ selector: 'redaction-confirm-delete-users-dialog', @@ -40,7 +41,7 @@ export class ConfirmDeleteUsersDialogComponent { async deleteUser() { if (this.valid) { this._loadingService.start(); - await this._userService.delete(this.userIds).toPromise(); + await firstValueFrom(this._userService.delete(this.userIds)); this.dialogRef.close(true); } else { this.showToast = true; diff --git a/apps/red-ui/src/app/modules/admin/dialogs/edit-color-dialog/edit-color-dialog.component.html b/apps/red-ui/src/app/modules/admin/dialogs/edit-color-dialog/edit-color-dialog.component.html index 1d42f37a4..d77ea8f49 100644 --- a/apps/red-ui/src/app/modules/admin/dialogs/edit-color-dialog/edit-color-dialog.component.html +++ b/apps/red-ui/src/app/modules/admin/dialogs/edit-color-dialog/edit-color-dialog.component.html @@ -28,11 +28,11 @@
-
- +
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 519eb2dd2..2f544c1d0 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,4 +1,4 @@ -import { Component, Inject } from '@angular/core'; +import { Component, Inject, Injector } from '@angular/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { DefaultColorType, IColors } from '@red/domain'; import { BaseDialogComponent, Toaster } from '@iqser/common-ui'; @@ -7,6 +7,7 @@ import { TranslateService } from '@ngx-translate/core'; import { defaultColorsTranslations } from '../../translations/default-colors-translations'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { DictionaryService } from '@shared/services/dictionary.service'; +import { firstValueFrom } from 'rxjs'; interface IEditColorData { colors: IColors; @@ -19,10 +20,7 @@ interface IEditColorData { styleUrls: ['./edit-color-dialog.component.scss'], }) export class EditColorDialogComponent extends BaseDialogComponent { - readonly form: FormGroup; translations = defaultColorsTranslations; - readonly disabled = false; - private readonly _initialColor: string; private readonly _dossierTemplateId: string; constructor( @@ -30,23 +28,16 @@ export class EditColorDialogComponent extends BaseDialogComponent { private readonly _dictionaryService: DictionaryService, private readonly _toaster: Toaster, private readonly _translateService: TranslateService, - private readonly _dialogRef: MatDialogRef, + protected readonly _injector: Injector, + protected readonly _dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) readonly data: IEditColorData, ) { - super(); + super(_injector, _dialogRef); this._dossierTemplateId = data.dossierTemplateId; - this._initialColor = data.colors[data.colorKey]; this.form = this._getForm(); - } - - get changed(): boolean { - return this.form.get('color').value !== this._initialColor; - } - - get valid(): boolean { - return this.form.valid; + this.initialFormValue = this.form.getRawValue(); } async save() { @@ -56,7 +47,7 @@ export class EditColorDialogComponent extends BaseDialogComponent { }; try { - await this._dictionaryService.setColors(colors, this._dossierTemplateId).toPromise(); + await firstValueFrom(this._dictionaryService.setColors(colors, this._dossierTemplateId)); this._dialogRef.close(true); const color = this._translateService.instant(defaultColorsTranslations[this.data.colorKey]); this._toaster.info(_('edit-color-dialog.success'), { params: { color: color } }); 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 5f95a12ad..496abd8f0 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 @@ -2,9 +2,9 @@ import { ChangeDetectionStrategy, Component, Inject, Injector } from '@angular/c import { AbstractControl, FormBuilder, FormGroup, ValidatorFn, Validators } from '@angular/forms'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import * as Papa from 'papaparse'; -import { Observable } from 'rxjs'; +import { firstValueFrom, Observable } from 'rxjs'; import { map, startWith } from 'rxjs/operators'; -import { DefaultListingServices, ListingComponent, TableColumnConfig, Toaster, trackBy } from '@iqser/common-ui'; +import { DefaultListingServices, ListingComponent, TableColumnConfig, Toaster, trackByFactory } from '@iqser/common-ui'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { FileAttributeConfig, FileAttributeConfigTypes, IField, IFileAttributesConfig } from '@red/domain'; import { FileAttributesService } from '@services/entity-services/file-attributes.service'; @@ -34,7 +34,7 @@ export class FileAttributesCsvImportDialogComponent extends ListingComponent { @@ -183,7 +175,7 @@ export class FileAttributesCsvImportDialogComponent extends ListingComponent { if ((this.parseResult?.meta?.fields || []).indexOf(control.value) !== -1) { diff --git a/apps/red-ui/src/app/modules/admin/dialogs/smtp-auth-dialog/smtp-auth-dialog.component.html b/apps/red-ui/src/app/modules/admin/dialogs/smtp-auth-dialog/smtp-auth-dialog.component.html index 55d87f45c..ab82c0fe3 100644 --- a/apps/red-ui/src/app/modules/admin/dialogs/smtp-auth-dialog/smtp-auth-dialog.component.html +++ b/apps/red-ui/src/app/modules/admin/dialogs/smtp-auth-dialog/smtp-auth-dialog.component.html @@ -27,5 +27,5 @@ - + diff --git a/apps/red-ui/src/app/modules/admin/dialogs/smtp-auth-dialog/smtp-auth-dialog.component.ts b/apps/red-ui/src/app/modules/admin/dialogs/smtp-auth-dialog/smtp-auth-dialog.component.ts index a30311fcf..d5cc95383 100644 --- a/apps/red-ui/src/app/modules/admin/dialogs/smtp-auth-dialog/smtp-auth-dialog.component.ts +++ b/apps/red-ui/src/app/modules/admin/dialogs/smtp-auth-dialog/smtp-auth-dialog.component.ts @@ -1,23 +1,27 @@ -import { Component, Inject } from '@angular/core'; +import { Component, Inject, Injector } from '@angular/core'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { UserService } from '@services/user.service'; import { ISmtpConfiguration } from '@red/domain'; +import { BaseDialogComponent } from '@iqser/common-ui'; @Component({ selector: 'redaction-smtp-auth-dialog', templateUrl: './smtp-auth-dialog.component.html', styleUrls: ['./smtp-auth-dialog.component.scss'], }) -export class SmtpAuthDialogComponent { - readonly form: FormGroup = this._getForm(); - +export class SmtpAuthDialogComponent extends BaseDialogComponent { constructor( private readonly _formBuilder: FormBuilder, private readonly _userService: UserService, - public dialogRef: MatDialogRef, + protected readonly _injector: Injector, + protected readonly _dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data: ISmtpConfiguration, - ) {} + ) { + super(_injector, _dialogRef); + this.form = this._getForm(); + this.initialFormValue = this.form.getRawValue(); + } private _getForm(): FormGroup { return this._formBuilder.group({ @@ -27,6 +31,6 @@ export class SmtpAuthDialogComponent { } save() { - this.dialogRef.close(this.form.getRawValue()); + this._dialogRef.close(this.form.getRawValue()); } } 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 296eb49c8..0d7278f2d 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 @@ -60,7 +60,6 @@ *ngIf="form.get('userId').value !== ALL_USERS" [user]="form.get('userId').value" [withName]="true" - size="small" >
@@ -69,7 +68,6 @@ *ngIf="userId !== ALL_USERS" [user]="userId" [withName]="true" - size="small" >
@@ -109,7 +107,7 @@
- +
diff --git a/apps/red-ui/src/app/modules/admin/screens/audit/audit-screen.component.scss b/apps/red-ui/src/app/modules/admin/screens/audit/audit-screen.component.scss index 010d52fd9..fee44202a 100644 --- a/apps/red-ui/src/app/modules/admin/screens/audit/audit-screen.component.scss +++ b/apps/red-ui/src/app/modules/admin/screens/audit/audit-screen.component.scss @@ -17,11 +17,3 @@ form { font-size: 16px; opacity: 0.7; } - -.mr-0 { - margin-right: 0; -} - -.mr-20 { - margin-right: 20px; -} 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 b803e3549..1a0d3508c 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 @@ -8,6 +8,7 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { UserService } from '@services/user.service'; import { Audit, IAudit, IAuditResponse, IAuditSearchRequest } from '@red/domain'; import { AuditService } from '../../services/audit.service'; +import { firstValueFrom } from 'rxjs'; const PAGE_SIZE = 50; @@ -52,15 +53,6 @@ export class AuditScreenComponent extends ListingComponent implements OnD }); } - private _getForm(): FormGroup { - return this._formBuilder.group({ - category: [this.ALL_CATEGORIES], - userId: [this.ALL_USERS], - from: [], - to: [], - }); - } - get totalPages(): number { if (!this.logs) { return 0; @@ -76,6 +68,15 @@ export class AuditScreenComponent extends ListingComponent implements OnD await this._fetchData(); } + private _getForm(): FormGroup { + return this._formBuilder.group({ + category: [this.ALL_CATEGORIES], + userId: [this.ALL_USERS], + from: [], + to: [], + }); + } + private _updateDateFilters(value): boolean { if (applyIntervalConstraints(value, this._previousFrom, this._previousTo, this.form, 'from', 'to')) { return true; @@ -105,8 +106,8 @@ export class AuditScreenComponent extends ListingComponent implements OnD to, }; - promises.push(this._auditService.getCategories().toPromise()); - promises.push(this._auditService.searchAuditLog(logsRequestBody).toPromise()); + promises.push(firstValueFrom(this._auditService.getCategories())); + promises.push(firstValueFrom(this._auditService.searchAuditLog(logsRequestBody))); const data = await Promise.all(promises); this.categories = data[0].map(c => c.category); 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 8e7cd2833..747671e52 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 @@ -1,6 +1,6 @@