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/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/models/file/annotation.permissions.ts b/apps/red-ui/src/app/models/file/annotation.permissions.ts index 95bf99e30..a6ad75177 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; 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 94bd2ad99..9ba422ab9 100644 --- a/apps/red-ui/src/app/models/file/annotation.wrapper.ts +++ b/apps/red-ui/src/app/models/file/annotation.wrapper.ts @@ -283,11 +283,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; } 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 8c5f7e34b..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,34 +1,46 @@ -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'; +import { BehaviorSubject } from 'rxjs'; export class FileDataModel { static readonly DELTA_VIEW_TIME = 10 * 60 * 1000; // 10 minutes; - - hasChangeLog: boolean; allAnnotations: AnnotationWrapper[]; + readonly hasChangeLog$ = new BehaviorSubject(false); + readonly blob$ = new BehaviorSubject(undefined); + readonly file$ = new BehaviorSubject(undefined); constructor( - public file: File, - public fileData: Blob, + 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(); } + get file(): File { + return this.file$.value; + } + + set file(file: File) { + this.file$.next(file); + } + + get redactionLog(): IRedactionLog { + return this._redactionLog; + } + set redactionLog(redactionLog: IRedactionLog) { this._redactionLog = redactionLog; this._buildAllAnnotations(); } - get redactionLog() { - return this._redactionLog; - } - getVisibleAnnotations(viewMode: ViewMode) { return this.allAnnotations.filter(annotation => { if (viewMode === 'STANDARD') { @@ -47,7 +59,7 @@ export class FileDataModel { const previousAnnotations = this.allAnnotations || []; this.allAnnotations = entries .map(entry => AnnotationWrapper.fromData(entry)) - .filter(ann => ann.manual || !this.file.excludedPages.includes(ann.pageNumber)); + .filter(ann => ann.manual || !this._file.excludedPages.includes(ann.pageNumber)); if (!this._areDevFeaturesEnabled) { this.allAnnotations = this.allAnnotations.filter(annotation => !annotation.isFalsePositive); @@ -121,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(); @@ -134,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/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..94a52a5c3 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 @@ -7,25 +7,25 @@ }} -
-
-
- - - {{ translations[type.toLocaleLowerCase()] | translate }} -
-
+
+ + + + + + +
-
-
+
+
{{ translations[preference] | translate }} 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..73657ced5 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 @@ -4,8 +4,12 @@ import { notificationsTranslations } from '../../../translations/notifications-t import { NotificationPreferencesService } from '../../../services/notification-preferences.service'; import { 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'; @Component({ selector: 'redaction-notifications-screen', @@ -33,16 +37,6 @@ export class NotificationsScreenComponent implements OnInit { await this._initializeForm(); } - private _getForm(): FormGroup { - return this._formBuilder.group({ - inAppNotificationsEnabled: [undefined], - emailNotificationsEnabled: [undefined], - emailNotificationType: [undefined], - emailNotifications: [undefined], - inAppNotifications: [undefined], - }); - } - isCategoryActive(category: string) { return this.formGroup.get(`${category}Enabled`).value; } @@ -80,6 +74,16 @@ export class NotificationsScreenComponent implements OnInit { this._loadingService.stop(); } + private _getForm(): FormGroup { + return this._formBuilder.group({ + inAppNotificationsEnabled: [undefined], + emailNotificationsEnabled: [undefined], + emailNotificationType: [undefined], + emailNotifications: [undefined], + inAppNotifications: [undefined], + }); + } + private async _initializeForm() { this._loadingService.start(); 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 6531660b4..566ae678e 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 @@ -73,7 +73,7 @@ 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 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/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 903caceaf..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.ts b/apps/red-ui/src/app/modules/admin/dialogs/add-edit-dictionary-dialog/add-edit-dictionary-dialog.component.ts index 737d96915..0c80cb245 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,4 +1,4 @@ -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'; @@ -6,7 +6,7 @@ import { BaseDialogComponent, shareDistinctLast, Toaster } from '@iqser/common-u 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), @@ -126,11 +105,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 0eaf795a8..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 abf0d181d..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 f42bebd94..4d6b575bd 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,6 +1,6 @@ -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'; @@ -17,7 +17,6 @@ import { HttpStatusCode } from '@angular/common/http'; 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,66 +24,50 @@ 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, + protected readonly _injector: Injector, + protected readonly _dialogRef: MatDialogRef, private readonly _loadingService: LoadingService, - public dialogRef: MatDialogRef, @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() { @@ -95,10 +78,10 @@ export class AddEditDossierTemplateDialogComponent extends BaseDialogComponent { ...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._appStateService.loadDictionaryData(); - this.dialogRef.close(true); + this._dialogRef.close(true); } catch (error: any) { const message = error.status === HttpStatusCode.Conflict @@ -136,7 +119,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 82f32ef3e..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/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..0320cf449 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 @@ -134,13 +134,14 @@ export class UserDetailsComponent extends AutoUnsubscribe implements OnChanges, } private _getForm(): FormGroup { + console.log(this.user); return this._formBuilder.group({ firstName: [this.user?.firstName, Validators.required], lastName: [this.user?.lastName, Validators.required], 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/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 9e4bcb124..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..87fd53d62 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'; @@ -19,10 +19,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 +27,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() { diff --git a/apps/red-ui/src/app/modules/admin/screens/digital-signature/digital-signature-screen.component.ts b/apps/red-ui/src/app/modules/admin/screens/digital-signature/digital-signature-screen.component.ts index 66eabc70e..732c73209 100644 --- a/apps/red-ui/src/app/modules/admin/screens/digital-signature/digital-signature-screen.component.ts +++ b/apps/red-ui/src/app/modules/admin/screens/digital-signature/digital-signature-screen.component.ts @@ -6,7 +6,7 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { UserService } from '@services/user.service'; import { RouterHistoryService } from '@services/router-history.service'; import { DigitalSignatureService } from '../../services/digital-signature.service'; -import { IDigitalSignature } from '@red/domain'; +import { IDigitalSignature, IDigitalSignatureRequest } from '@red/domain'; import { HttpStatusCode } from '@angular/common/http'; @Component({ @@ -40,29 +40,30 @@ export class DigitalSignatureScreenComponent extends AutoUnsubscribe implements } saveDigitalSignature() { - const digitalSignature = { - ...this.form.getRawValue(), + const formValue = this.form.getRawValue(); + const digitalSignature: IDigitalSignature = { + ...formValue, }; //adjusted for chrome auto-complete / password manager - digitalSignature.password = digitalSignature.keySecret; + digitalSignature.password = formValue.keySecret; const observable = this.digitalSignatureExists ? this._digitalSignatureService.update(digitalSignature) : this._digitalSignatureService.save(digitalSignature); - this.addSubscription = observable.subscribe( - () => { + this.addSubscription = observable.subscribe({ + next: () => { this.loadDigitalSignatureAndInitializeForm(); this._toaster.success(_('digital-signature-screen.action.save-success')); }, - error => { + error: error => { if (error.status === HttpStatusCode.BadRequest) { this._toaster.error(_('digital-signature-screen.action.certificate-not-valid-error')); } else { this._toaster.error(_('digital-signature-screen.action.save-error')); } }, - ); + }); } removeDigitalSignature() { @@ -85,23 +86,23 @@ export class DigitalSignatureScreenComponent extends AutoUnsubscribe implements this.form.get('certificateName').setValue(file.name); input.value = null; }; - fileReader.readAsDataURL(file); + fileReader.readAsDataURL(file as Blob); } loadDigitalSignatureAndInitializeForm() { this._loadingService.start(); - this.addSubscription = this._digitalSignatureService + this._digitalSignatureService .getSignature() - .subscribe( - digitalSignature => { + .subscribe({ + next: digitalSignature => { this.digitalSignatureExists = true; this.digitalSignature = digitalSignature; }, - () => { + error: () => { this.digitalSignatureExists = false; this.digitalSignature = {}; }, - ) + }) .add(() => { this.form = this._getForm(); this._loadingService.stop(); diff --git a/apps/red-ui/src/app/modules/admin/screens/justifications/justifications-screen/justifications-screen.component.html b/apps/red-ui/src/app/modules/admin/screens/justifications/justifications-screen/justifications-screen.component.html index 9d77bbc85..80a96d3d8 100644 --- a/apps/red-ui/src/app/modules/admin/screens/justifications/justifications-screen/justifications-screen.component.html +++ b/apps/red-ui/src/app/modules/admin/screens/justifications/justifications-screen/justifications-screen.component.html @@ -11,7 +11,7 @@ , + readonly userPreferenceService: UserPreferenceService, readonly userService: UserService, ) {} diff --git a/apps/red-ui/src/app/modules/admin/screens/license-information/license-information-screen.component.ts b/apps/red-ui/src/app/modules/admin/screens/license-information/license-information-screen.component.ts index 00006cde9..35abc60ff 100644 --- a/apps/red-ui/src/app/modules/admin/screens/license-information/license-information-screen.component.ts +++ b/apps/red-ui/src/app/modules/admin/screens/license-information/license-information-screen.component.ts @@ -8,6 +8,7 @@ import { UserService } from '@services/user.service'; import { RouterHistoryService } from '@services/router-history.service'; import { LicenseReportService } from '../../services/licence-report.service'; import { ILicenseReport } from '@red/domain'; +import { Color, ScaleType } from '@swimlane/ngx-charts'; @Component({ selector: 'redaction-license-information-screen', @@ -31,14 +32,16 @@ export class LicenseInformationScreenComponent implements OnInit { analysisPercentageOfLicense = 100; barChart: any[]; lineChartSeries: any[] = []; - lineChartScheme = { + lineChartScheme: Color = { + name: 'Line chart scheme', selectable: true, - group: 'Ordinal', + group: ScaleType.Ordinal, domain: ['#dd4d50', '#5ce594', '#0389ec'], }; - comboBarScheme = { + comboBarScheme: Color = { + name: 'Combo bar scheme', selectable: true, - group: 'Ordinal', + group: ScaleType.Ordinal, domain: ['#0389ec'], }; diff --git a/apps/red-ui/src/app/modules/admin/screens/reports/reports-screen.component.ts b/apps/red-ui/src/app/modules/admin/screens/reports/reports-screen.component.ts index 3d34001ee..791e5d5c1 100644 --- a/apps/red-ui/src/app/modules/admin/screens/reports/reports-screen.component.ts +++ b/apps/red-ui/src/app/modules/admin/screens/reports/reports-screen.component.ts @@ -92,6 +92,31 @@ export class ReportsScreenComponent implements OnInit { } const dossierTemplateId = this._dossierTemplatesService.activeDossierTemplateId; + + if (this.availableTemplates.some(template => template.fileName === file.name)) { + const data = new ConfirmationDialogInput({ + title: _('confirmation-dialog.report-template-same-name.title'), + question: _('confirmation-dialog.report-template-same-name.question'), + confirmationText: _('confirmation-dialog.report-template-same-name.confirmation-text'), + denyText: _('confirmation-dialog.report-template-same-name.deny-text'), + translateParams: { + fileName: file.name, + }, + }); + + this._dialogService.openDialog('confirm', null, data, null, async result => { + if (result) { + await this._openConfirmationDialog(file, dossierTemplateId); + } + }); + } else { + await this._openConfirmationDialog(file, dossierTemplateId); + } + + this._fileInput.nativeElement.value = null; + } + + private async _openConfirmationDialog(file: File, dossierTemplateId: string) { if (this._isExcelFile(file)) { const data = new ConfirmationDialogInput({ title: _('confirmation-dialog.upload-report-template.title'), @@ -113,7 +138,6 @@ export class ReportsScreenComponent implements OnInit { await this._reportTemplateService.uploadTemplateForm(dossierTemplateId, false, file).toPromise(); await this._loadReportTemplates(); } - this._fileInput.nativeElement.value = null; } private async _deleteTemplate(template: IReportTemplate) { diff --git a/apps/red-ui/src/app/modules/admin/services/admin-dialog.service.ts b/apps/red-ui/src/app/modules/admin/services/admin-dialog.service.ts index 8eaf8a2b5..66efb8fec 100644 --- a/apps/red-ui/src/app/modules/admin/services/admin-dialog.service.ts +++ b/apps/red-ui/src/app/modules/admin/services/admin-dialog.service.ts @@ -34,6 +34,7 @@ export class AdminDialogService extends DialogService { protected readonly _config: DialogConfig = { confirm: { component: ConfirmationDialogComponent, + dialogConfig: { disableClose: false }, }, addEditDictionary: { component: AddEditDictionaryDialogComponent, @@ -49,18 +50,19 @@ export class AdminDialogService extends DialogService { }, deleteFileAttribute: { component: ConfirmDeleteFileAttributeDialogComponent, + dialogConfig: { disableClose: false }, }, importFileAttributes: { component: FileAttributesCsvImportDialogComponent, - dialogConfig: largeDialogConfig, + dialogConfig: { ...largeDialogConfig, ...{ disableClose: false } }, }, deleteUsers: { component: ConfirmDeleteUsersDialogComponent, - dialogConfig: { autoFocus: true }, + dialogConfig: { autoFocus: true, disableClose: false }, }, addEditUser: { component: AddEditUserDialogComponent, - dialogConfig: { autoFocus: true }, + dialogConfig: { autoFocus: true, disableClose: false }, }, smtpAuthConfig: { component: SmtpAuthDialogComponent, diff --git a/apps/red-ui/src/app/modules/auth/red-role.guard.ts b/apps/red-ui/src/app/modules/auth/red-role.guard.ts index 524ba77e2..9ac205529 100644 --- a/apps/red-ui/src/app/modules/auth/red-role.guard.ts +++ b/apps/red-ui/src/app/modules/auth/red-role.guard.ts @@ -28,6 +28,7 @@ export class RedRoleGuard implements CanActivate { if ( this._userService.currentUser.isUserAdmin && !this._userService.currentUser.isAdmin && + !this._userService.currentUser.isUser && !(state.url.startsWith('/main/admin/users') || state.url.startsWith('/main/my-profile')) ) { this._router.navigate(['/main/admin/users']); diff --git a/apps/red-ui/src/app/modules/dossier/dialogs/add-dossier-dialog/add-dossier-dialog.component.html b/apps/red-ui/src/app/modules/dossier/dialogs/add-dossier-dialog/add-dossier-dialog.component.html index e4b6d7c77..726110f51 100644 --- a/apps/red-ui/src/app/modules/dossier/dialogs/add-dossier-dialog/add-dossier-dialog.component.html +++ b/apps/red-ui/src/app/modules/dossier/dialogs/add-dossier-dialog/add-dossier-dialog.component.html @@ -1,5 +1,5 @@
-
+
@@ -91,7 +91,7 @@ ({ key: type, @@ -30,10 +29,13 @@ export class AddDossierDialogComponent { private readonly _dossierTemplatesService: DossierTemplatesService, private readonly _formBuilder: FormBuilder, private readonly _reportTemplateController: ReportTemplateService, - readonly dialogRef: MatDialogRef, + protected readonly _injector: Injector, + protected readonly _dialogRef: MatDialogRef, ) { + super(_injector, _dialogRef); this._getDossierTemplates(); this.form = this._getForm(); + this.initialFormValue = this.form.getRawValue(); } private _getForm(): FormGroup { @@ -75,10 +77,10 @@ export class AddDossierDialogComponent { reportTemplateValueMapper = (reportTemplate: IReportTemplate) => reportTemplate.templateId; - async saveDossier(addMembers = false) { + async save(options?: SaveOptions) { const savedDossier = await this._dossiersService.createOrUpdate(this._formToObject()).toPromise(); if (savedDossier) { - this.dialogRef.close({ dossier: savedDossier, addMembers }); + this._dialogRef.close({ dossier: savedDossier, addMembers: options?.addMembers }); } } diff --git a/apps/red-ui/src/app/modules/dossier/dialogs/change-legal-basis-dialog/change-legal-basis-dialog.component.html b/apps/red-ui/src/app/modules/dossier/dialogs/change-legal-basis-dialog/change-legal-basis-dialog.component.html index 97a07755e..5395eb4df 100644 --- a/apps/red-ui/src/app/modules/dossier/dialogs/change-legal-basis-dialog/change-legal-basis-dialog.component.html +++ b/apps/red-ui/src/app/modules/dossier/dialogs/change-legal-basis-dialog/change-legal-basis-dialog.component.html @@ -38,12 +38,12 @@
-
- +
diff --git a/apps/red-ui/src/app/modules/dossier/dialogs/change-legal-basis-dialog/change-legal-basis-dialog.component.ts b/apps/red-ui/src/app/modules/dossier/dialogs/change-legal-basis-dialog/change-legal-basis-dialog.component.ts index 16f9f5e8f..fcfe2d5d7 100644 --- a/apps/red-ui/src/app/modules/dossier/dialogs/change-legal-basis-dialog/change-legal-basis-dialog.component.ts +++ b/apps/red-ui/src/app/modules/dossier/dialogs/change-legal-basis-dialog/change-legal-basis-dialog.component.ts @@ -1,4 +1,4 @@ -import { Component, Inject, OnInit } from '@angular/core'; +import { Component, Inject, Injector, OnInit } from '@angular/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { AnnotationWrapper } from '@models/file/annotation.wrapper'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; @@ -6,6 +6,7 @@ import { PermissionsService } from '@services/permissions.service'; import { DossiersService } from '@services/entity-services/dossiers.service'; import { JustificationsService } from '@services/entity-services/justifications.service'; import { Dossier } from '@red/domain'; +import { BaseDialogComponent } from '@iqser/common-ui'; export interface LegalBasisOption { label?: string; @@ -16,8 +17,7 @@ export interface LegalBasisOption { @Component({ templateUrl: './change-legal-basis-dialog.component.html', }) -export class ChangeLegalBasisDialogComponent implements OnInit { - form: FormGroup = this._getForm(); +export class ChangeLegalBasisDialogComponent extends BaseDialogComponent implements OnInit { isDocumentAdmin: boolean; legalOptions: LegalBasisOption[] = []; @@ -26,16 +26,12 @@ export class ChangeLegalBasisDialogComponent implements OnInit { private readonly _dossiersService: DossiersService, private readonly _permissionsService: PermissionsService, private readonly _formBuilder: FormBuilder, - readonly dialogRef: MatDialogRef, + protected readonly _injector: Injector, + protected readonly _dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) private readonly _data: { annotations: AnnotationWrapper[]; dossier: Dossier }, - ) {} - - get changed(): boolean { - return ( - this.form.get('reason').value.legalBasis !== this._data.annotations[0].legalBasis || - this.form.get('section').value !== this._data.annotations[0].section || - this.form.get('classification').value !== this._data.annotations[0].value - ); + ) { + super(_injector, _dialogRef); + this.form = this._getForm(); } get allRectangles(): boolean { @@ -43,6 +39,7 @@ export class ChangeLegalBasisDialogComponent implements OnInit { } async ngOnInit() { + super.ngOnInit(); const data = await this._justificationsService.getForDossierTemplate(this._data.dossier.dossierTemplateId).toPromise(); this.legalOptions = data @@ -56,6 +53,7 @@ export class ChangeLegalBasisDialogComponent implements OnInit { this.form.patchValue({ reason: this.legalOptions.find(option => option.legalBasis === this._data.annotations[0].legalBasis), }); + this.initialFormValue = this.form.getRawValue(); } private _getForm(): FormGroup { @@ -69,7 +67,7 @@ export class ChangeLegalBasisDialogComponent implements OnInit { } save() { - this.dialogRef.close({ + this._dialogRef.close({ legalBasis: this.form.get('reason').value.legalBasis, section: this.form.get('section').value, comment: this.form.get('comment').value, diff --git a/apps/red-ui/src/app/modules/dossier/dialogs/document-info-dialog/document-info-dialog.component.html b/apps/red-ui/src/app/modules/dossier/dialogs/document-info-dialog/document-info-dialog.component.html index 2d8ddfe7c..49fadde08 100644 --- a/apps/red-ui/src/app/modules/dossier/dialogs/document-info-dialog/document-info-dialog.component.html +++ b/apps/red-ui/src/app/modules/dossier/dialogs/document-info-dialog/document-info-dialog.component.html @@ -1,7 +1,7 @@
-
+
@@ -9,11 +9,11 @@
-
- +
diff --git a/apps/red-ui/src/app/modules/dossier/dialogs/document-info-dialog/document-info-dialog.component.ts b/apps/red-ui/src/app/modules/dossier/dialogs/document-info-dialog/document-info-dialog.component.ts index b18ead053..8d4a77500 100644 --- a/apps/red-ui/src/app/modules/dossier/dialogs/document-info-dialog/document-info-dialog.component.ts +++ b/apps/red-ui/src/app/modules/dossier/dialogs/document-info-dialog/document-info-dialog.component.ts @@ -1,16 +1,17 @@ -import { Component, Inject, OnInit } from '@angular/core'; +import { Component, Inject, Injector, OnInit } from '@angular/core'; import { FormBuilder, FormGroup } from '@angular/forms'; import { Dossier, File, IFileAttributeConfig } from '@red/domain'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { FileAttributesService } from '@services/entity-services/file-attributes.service'; import { DossiersService } from '@services/entity-services/dossiers.service'; import { FilesService } from '@services/entity-services/files.service'; +import { BaseDialogComponent } from '@iqser/common-ui'; @Component({ templateUrl: './document-info-dialog.component.html', styleUrls: ['./document-info-dialog.component.scss'], }) -export class DocumentInfoDialogComponent implements OnInit { +export class DocumentInfoDialogComponent extends BaseDialogComponent implements OnInit { form: FormGroup; attributes: IFileAttributeConfig[]; @@ -21,27 +22,31 @@ export class DocumentInfoDialogComponent implements OnInit { private readonly _formBuilder: FormBuilder, private readonly _fileAttributesService: FileAttributesService, private readonly _filesService: FilesService, - public dialogRef: MatDialogRef, + protected readonly _injector: Injector, + protected readonly _dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) readonly data: File, ) { + super(_injector, _dialogRef); this._dossier = this._dossiersService.find(this.data.dossierId); } async ngOnInit() { + super.ngOnInit(); this.attributes = ( await this._fileAttributesService.getFileAttributesConfig(this._dossier.dossierTemplateId).toPromise() ).fileAttributeConfigs.filter(attr => attr.editable); this.form = this._getForm(); + this.initialFormValue = this.form.getRawValue(); } - async saveDocumentInfo() { + async save() { const attributeIdToValue = { ...this.data.fileAttributes?.attributeIdToValue, ...this.form.getRawValue(), }; await this._fileAttributesService.setFileAttributes({ attributeIdToValue }, this.data.dossierId, this.data.fileId).toPromise(); this._filesService.reload(this.data.dossierId, this.data.fileId); - this.dialogRef.close(true); + this._dialogRef.close(true); } private _getForm(): FormGroup { diff --git a/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/attributes/edit-dossier-attributes.component.ts b/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/attributes/edit-dossier-attributes.component.ts index e22722727..982311438 100644 --- a/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/attributes/edit-dossier-attributes.component.ts +++ b/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/attributes/edit-dossier-attributes.component.ts @@ -66,9 +66,10 @@ export class EditDossierAttributesComponent implements EditDossierSectionInterfa async save(): EditDossierSaveResult { const dossierAttributeList = this.attributes.map(attr => ({ dossierAttributeConfigId: attr.id, - value: this.isSpecificType(attr, DossierAttributeConfigTypes.DATE) - ? moment(this.currentAttrValue(attr)).format('YYYY-MM-DD') - : this.currentAttrValue(attr), + value: + this.isSpecificType(attr, DossierAttributeConfigTypes.DATE) && !!this.currentAttrValue(attr) + ? moment(this.currentAttrValue(attr)).format('YYYY-MM-DD') + : this.currentAttrValue(attr), })); try { await this._dossierAttributesService.setAttributes(this.dossier, dossierAttributeList).toPromise(); diff --git a/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/dictionary/edit-dossier-dictionary.component.html b/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/dictionary/edit-dossier-dictionary.component.html index 6cd5f9d5f..6aec3866c 100644 --- a/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/dictionary/edit-dossier-dictionary.component.html +++ b/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/dictionary/edit-dossier-dictionary.component.html @@ -1,12 +1,22 @@
-
-
{{ dossierDictionary?.label }}
-
-
- - {{ 'edit-dossier-dialog.dictionary.entries' | translate: { length: (dossierDictionary?.entries || []).length } }} +
+
+
{{ dossierDictionary?.label }}
+
+
+ + {{ 'edit-dossier-dialog.dictionary.entries' | translate: { length: (dossierDictionary?.entries || []).length } }} +
+ +
+
+ + {{ 'add-edit-dictionary.form.add-to-dictionary-action' | translate }} + +
+
diff --git a/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/dictionary/edit-dossier-dictionary.component.scss b/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/dictionary/edit-dossier-dictionary.component.scss index 84c64d950..db87a56e3 100644 --- a/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/dictionary/edit-dossier-dictionary.component.scss +++ b/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/dictionary/edit-dossier-dictionary.component.scss @@ -2,6 +2,14 @@ display: flex; justify-content: space-between; + .header-left { + display: flex; + + .iqser-input-group { + margin-left: 24px; + } + } + .display-name { display: flex; align-items: center; diff --git a/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/dictionary/edit-dossier-dictionary.component.ts b/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/dictionary/edit-dossier-dictionary.component.ts index 2e309c464..c575f4b5c 100644 --- a/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/dictionary/edit-dossier-dictionary.component.ts +++ b/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/dictionary/edit-dossier-dictionary.component.ts @@ -7,6 +7,7 @@ import { DictionaryService } from '@shared/services/dictionary.service'; import { CircleButtonTypes, LoadingService, Toaster } from '@iqser/common-ui'; import { DossiersService } from '@services/entity-services/dossiers.service'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; @Component({ selector: 'redaction-edit-dossier-dictionary', @@ -15,10 +16,13 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; }) export class EditDossierDictionaryComponent implements EditDossierSectionInterface, OnInit { @Input() dossier: Dossier; + + form: FormGroup; canEdit = false; - readonly circleButtonTypes = CircleButtonTypes; dossierDictionary: IDictionary; + readonly circleButtonTypes = CircleButtonTypes; + @ViewChild(DictionaryManagerComponent, { static: false }) private readonly _dictionaryManager: DictionaryManagerComponent; constructor( @@ -26,11 +30,20 @@ export class EditDossierDictionaryComponent implements EditDossierSectionInterfa private readonly _dictionaryService: DictionaryService, private readonly _permissionsService: PermissionsService, private readonly _loadingService: LoadingService, + private readonly _formBuilder: FormBuilder, private readonly _toaster: Toaster, ) {} + get formChanged() { + if (this.form) { + return this.form.get('addToDictionaryAction').value !== this.dossierDictionary.addToDictionaryAction; + } + + return false; + } + get changed(): boolean { - return this._dictionaryManager.editor.hasChanges; + return this._dictionaryManager.editor.hasChanges || this.formChanged; } get disabled(): boolean { @@ -38,13 +51,14 @@ export class EditDossierDictionaryComponent implements EditDossierSectionInterfa } get valid(): boolean { - return this._dictionaryManager.editor.hasChanges; + return this._dictionaryManager.editor.hasChanges || this.formChanged; } async ngOnInit() { this._loadingService.start(); this.canEdit = this._permissionsService.isDossierMember(this.dossier); await this._updateDossierDictionary(); + this.form = this._getForm(); this._loadingService.stop(); } @@ -66,6 +80,15 @@ export class EditDossierDictionaryComponent implements EditDossierSectionInterfa async save(): EditDossierSaveResult { try { + const dictionary: IDictionary = { + ...this.dossierDictionary, + type: 'dossier_redaction', + addToDictionaryAction: this.form.get('addToDictionaryAction').value, + }; + await this._dictionaryService + .updateDictionary(dictionary, this.dossier.dossierTemplateId, 'dossier_redaction', this.dossier.id) + .toPromise(); + await this._dictionaryService .saveEntries( this._dictionaryManager.editor.currentEntries, @@ -76,6 +99,7 @@ export class EditDossierDictionaryComponent implements EditDossierSectionInterfa false, ) .toPromise(); + await this._updateDossierDictionary(); return { success: true }; } catch (error) { @@ -85,6 +109,20 @@ export class EditDossierDictionaryComponent implements EditDossierSectionInterfa revert() { this._dictionaryManager.revert(); + this.form.reset({ + addToDictionaryAction: this.dossierDictionary.addToDictionaryAction, + }); + } + + private _getForm(): FormGroup { + return this._formBuilder.group({ + addToDictionaryAction: [ + { + value: this.dossierDictionary.addToDictionaryAction, + disabled: !this._permissionsService.isOwner(this.dossier), + }, + ], + }); } private async _updateDossierDictionary() { diff --git a/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/edit-dossier-dialog.component.html b/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/edit-dossier-dialog.component.html index c43e2df9e..ea40e9549 100644 --- a/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/edit-dossier-dialog.component.html +++ b/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/edit-dossier-dialog.component.html @@ -53,7 +53,7 @@ {{ 'edit-dossier-dialog.actions.save' | translate }}
- + diff --git a/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/edit-dossier-dialog.component.ts b/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/edit-dossier-dialog.component.ts index e319283ab..486375e34 100644 --- a/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/edit-dossier-dialog.component.ts +++ b/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/edit-dossier-dialog.component.ts @@ -1,10 +1,10 @@ -import { ChangeDetectorRef, Component, Inject, ViewChild } from '@angular/core'; +import { ChangeDetectorRef, Component, Inject, Injector, ViewChild } from '@angular/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { Dossier } from '@red/domain'; import { EditDossierGeneralInfoComponent } from './general-info/edit-dossier-general-info.component'; import { EditDossierDownloadPackageComponent } from './download-package/edit-dossier-download-package.component'; import { EditDossierSectionInterface } from './edit-dossier-section.interface'; -import { BaseDialogComponent, IconButtonTypes, LoadingService, Toaster } from '@iqser/common-ui'; +import { BaseDialogComponent, ConfirmOptions, IconButtonTypes, LoadingService, SaveOptions, Toaster } from '@iqser/common-ui'; import { EditDossierDictionaryComponent } from './dictionary/edit-dossier-dictionary.component'; import { EditDossierAttributesComponent } from './attributes/edit-dossier-attributes.component'; @@ -40,16 +40,17 @@ export class EditDossierDialogComponent extends BaseDialogComponent { private readonly _toaster: Toaster, private readonly _dossiersService: DossiersService, private readonly _changeRef: ChangeDetectorRef, - private readonly _dialogRef: MatDialogRef, private readonly _loadingService: LoadingService, private readonly _permissionsService: PermissionsService, + protected readonly _injector: Injector, + protected readonly _dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) private readonly _data: { dossierId: string; section?: Section; }, ) { - super(); + super(_injector, _dialogRef); this.navItems = [ { key: 'dossierInfo', @@ -126,7 +127,7 @@ export class EditDossierDialogComponent extends BaseDialogComponent { return this.activeComponent?.disabled; } - async save(closeAfterSave: boolean = false) { + async save(options?: SaveOptions) { this._loadingService.start(); const result = await this.activeComponent.save(); this._loadingService.stop(); @@ -135,7 +136,7 @@ export class EditDossierDialogComponent extends BaseDialogComponent { this._toaster.success(_('edit-dossier-dialog.change-successful'), { params: { dossierName: this._dossierName } }); } - if (result.success && closeAfterSave) { + if (result.success && options?.closeAfterSave) { this._dialogRef.close(); } } @@ -146,9 +147,19 @@ export class EditDossierDialogComponent extends BaseDialogComponent { changeTab(key: Section) { if (this.changed) { - this._toaster.error(_('edit-dossier-dialog.unsaved-changes')); - return; + this._openConfirmDialog().then(async result => { + if (result in ConfirmOptions) { + if (result === ConfirmOptions.CONFIRM) { + await this.save(); + } else { + this.revert(); + } + this.activeNav = key; + } + this._waitingForConfirmation = false; + }); + } else { + this.activeNav = key; } - this.activeNav = key; } } diff --git a/apps/red-ui/src/app/modules/dossier/dialogs/force-redaction-dialog/force-annotation-dialog.component.html b/apps/red-ui/src/app/modules/dossier/dialogs/force-redaction-dialog/force-annotation-dialog.component.html index fb2d27da6..be573e7aa 100644 --- a/apps/red-ui/src/app/modules/dossier/dialogs/force-redaction-dialog/force-annotation-dialog.component.html +++ b/apps/red-ui/src/app/modules/dossier/dialogs/force-redaction-dialog/force-annotation-dialog.component.html @@ -1,5 +1,5 @@
-
+
@@ -19,7 +19,7 @@
- +
@@ -29,11 +29,11 @@
-
- +
diff --git a/apps/red-ui/src/app/modules/dossier/dialogs/force-redaction-dialog/force-annotation-dialog.component.ts b/apps/red-ui/src/app/modules/dossier/dialogs/force-redaction-dialog/force-annotation-dialog.component.ts index 66fa3e626..3a69b28a3 100644 --- a/apps/red-ui/src/app/modules/dossier/dialogs/force-redaction-dialog/force-annotation-dialog.component.ts +++ b/apps/red-ui/src/app/modules/dossier/dialogs/force-redaction-dialog/force-annotation-dialog.component.ts @@ -1,7 +1,7 @@ -import { Component, Inject, OnInit } from '@angular/core'; +import { Component, Inject, Injector, OnInit } from '@angular/core'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; -import { Toaster } from '@iqser/common-ui'; +import { BaseDialogComponent, Toaster } from '@iqser/common-ui'; import { TranslateService } from '@ngx-translate/core'; import { UserService } from '@services/user.service'; import { ManualAnnotationService } from '../../services/manual-annotation.service'; @@ -21,8 +21,7 @@ export interface LegalBasisOption { templateUrl: './force-annotation-dialog.component.html', styleUrls: ['./force-annotation-dialog.component.scss'], }) -export class ForceAnnotationDialogComponent implements OnInit { - redactionForm: FormGroup; +export class ForceAnnotationDialogComponent extends BaseDialogComponent implements OnInit { isDocumentAdmin: boolean; legalOptions: LegalBasisOption[] = []; @@ -35,10 +34,13 @@ export class ForceAnnotationDialogComponent implements OnInit { private readonly _justificationsService: JustificationsService, private readonly _manualAnnotationService: ManualAnnotationService, private readonly _permissionsService: PermissionsService, - public dialogRef: MatDialogRef, + protected readonly _injector: Injector, + protected readonly _dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) private readonly _data: { readonly dossier: Dossier; readonly hint: boolean }, ) { - this.redactionForm = this._getForm(); + super(_injector, _dialogRef); + this.form = this._getForm(); + this.initialFormValue = this.form.getRawValue(); } get isHintDialog() { @@ -55,6 +57,7 @@ export class ForceAnnotationDialogComponent implements OnInit { } async ngOnInit() { + super.ngOnInit(); const data = await this._justificationsService.getForDossierTemplate(this._data.dossier.dossierTemplateId).toPromise(); this.legalOptions = data.map(lbm => ({ @@ -66,17 +69,17 @@ export class ForceAnnotationDialogComponent implements OnInit { this.legalOptions.sort((a, b) => a.label.localeCompare(b.label)); } - handleForceAnnotation() { - this.dialogRef.close(this._createForceRedactionRequest()); + save() { + this._dialogRef.close(this._createForceRedactionRequest()); } private _createForceRedactionRequest(): ILegalBasisChangeRequest { const request: ILegalBasisChangeRequest = {}; - const legalOption: LegalBasisOption = this.redactionForm.get('reason').value; + const legalOption: LegalBasisOption = this.form.get('reason').value; request.legalBasis = legalOption.legalBasis; - request.comment = this.redactionForm.get('comment').value; + request.comment = this.form.get('comment').value; return request; } diff --git a/apps/red-ui/src/app/modules/dossier/dialogs/manual-redaction-dialog/manual-annotation-dialog.component.html b/apps/red-ui/src/app/modules/dossier/dialogs/manual-redaction-dialog/manual-annotation-dialog.component.html index 46db18d59..00e585c58 100644 --- a/apps/red-ui/src/app/modules/dossier/dialogs/manual-redaction-dialog/manual-annotation-dialog.component.html +++ b/apps/red-ui/src/app/modules/dossier/dialogs/manual-redaction-dialog/manual-annotation-dialog.component.html @@ -1,5 +1,5 @@
-
+
@@ -54,7 +54,7 @@
- +
@@ -74,11 +74,11 @@
-
- +
diff --git a/apps/red-ui/src/app/modules/dossier/dialogs/manual-redaction-dialog/manual-annotation-dialog.component.ts b/apps/red-ui/src/app/modules/dossier/dialogs/manual-redaction-dialog/manual-annotation-dialog.component.ts index 710b4fc6b..f761f6cf0 100644 --- a/apps/red-ui/src/app/modules/dossier/dialogs/manual-redaction-dialog/manual-annotation-dialog.component.ts +++ b/apps/red-ui/src/app/modules/dossier/dialogs/manual-redaction-dialog/manual-annotation-dialog.component.ts @@ -1,4 +1,4 @@ -import { Component, Inject, OnInit } from '@angular/core'; +import { Component, Inject, Injector, OnInit } from '@angular/core'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { AppStateService } from '@state/app-state.service'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; @@ -9,6 +9,8 @@ import { PermissionsService } from '@services/permissions.service'; import { JustificationsService } from '@services/entity-services/justifications.service'; import { Dictionary, Dossier, File, IAddRedactionRequest } from '@red/domain'; import { DossiersService } from '@services/entity-services/dossiers.service'; +import { BaseDialogComponent } from '@iqser/common-ui'; +import { DictionaryService } from '@shared/services/dictionary.service'; export interface LegalBasisOption { label?: string; @@ -21,9 +23,7 @@ export interface LegalBasisOption { templateUrl: './manual-annotation-dialog.component.html', styleUrls: ['./manual-annotation-dialog.component.scss'], }) -export class ManualAnnotationDialogComponent implements OnInit { - redactionForm: FormGroup; - +export class ManualAnnotationDialogComponent extends BaseDialogComponent implements OnInit { isDocumentAdmin: boolean; isDictionaryRequest: boolean; isFalsePositiveRequest: boolean; @@ -40,18 +40,21 @@ export class ManualAnnotationDialogComponent implements OnInit { private readonly _manualAnnotationService: ManualAnnotationService, private readonly _permissionsService: PermissionsService, private readonly _dossiersService: DossiersService, + private readonly _dictionaryService: DictionaryService, public dialogRef: MatDialogRef, + protected readonly _injector: Injector, + protected readonly _dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data: { manualRedactionEntryWrapper: ManualRedactionEntryWrapper; file: File }, ) { + super(_injector, _dialogRef); this._dossier = this._dossiersService.find(this.data.file.dossierId); this.isDocumentAdmin = this._permissionsService.isApprover(this._dossier); this.isFalsePositiveRequest = this.data.manualRedactionEntryWrapper.type === 'FALSE_POSITIVE'; this.isDictionaryRequest = this.data.manualRedactionEntryWrapper.type === 'DICTIONARY' || this.isFalsePositiveRequest; - this.redactionForm = this._getForm(); - - this.possibleDictionaries = this._possibleDictionaries; + this.form = this._getForm(); + this.initialFormValue = this.form.getRawValue(); } get title() { @@ -59,17 +62,21 @@ export class ManualAnnotationDialogComponent implements OnInit { } get displayedDictionaryLabel() { - const dictType = this.redactionForm.get('dictionary').value; + const dictType = this.form.get('dictionary').value; if (dictType) { return this.possibleDictionaries.find(d => d.type === dictType).label; } return null; } - private get _possibleDictionaries(): Dictionary[] { + private async _getPossibleDictionaries(): Promise { const possibleDictionaries: Dictionary[] = []; const dossier = this._dossier; + const dossierDictionary = await this._dictionaryService + .getForType(dossier.dossierTemplateId, 'dossier_redaction', dossier.dossierId) + .toPromise(); + for (const key of Object.keys(this._appStateService.dictionaryData[dossier.dossierTemplateId])) { const dictionaryData = this._appStateService.getDictionary(key, dossier.dossierTemplateId); if (!dictionaryData.virtual && dictionaryData.addToDictionaryAction) { @@ -77,14 +84,21 @@ export class ManualAnnotationDialogComponent implements OnInit { } } + if (dossierDictionary.addToDictionaryAction) { + // TODO fix this in the backend + possibleDictionaries.push(new Dictionary({ ...dossierDictionary, type: 'dossier_redaction' })); + } + possibleDictionaries.sort((a, b) => a.label.localeCompare(b.label)); return possibleDictionaries; } async ngOnInit() { - const data = await this._justificationsService.getForDossierTemplate(this._dossier.dossierTemplateId).toPromise(); + super.ngOnInit(); + this.possibleDictionaries = await this._getPossibleDictionaries(); + const data = await this._justificationsService.getForDossierTemplate(this._dossier.dossierTemplateId).toPromise(); this.legalOptions = data.map(lbm => ({ legalBasis: lbm.reason, description: lbm.description, @@ -94,11 +108,11 @@ export class ManualAnnotationDialogComponent implements OnInit { this.legalOptions.sort((a, b) => a.label.localeCompare(b.label)); } - handleAddRedaction() { + save() { this._enhanceManualRedaction(this.data.manualRedactionEntryWrapper.manualRedactionEntry); this._manualAnnotationService.addAnnotation(this.data.manualRedactionEntryWrapper.manualRedactionEntry, this.data.file).subscribe( - response => this.dialogRef.close(new ManualAnnotationResponse(this.data.manualRedactionEntryWrapper, response)), - () => this.dialogRef.close(), + response => this._dialogRef.close(new ManualAnnotationResponse(this.data.manualRedactionEntryWrapper, response)), + () => this._dialogRef.close(), ); } @@ -123,22 +137,26 @@ export class ManualAnnotationDialogComponent implements OnInit { } private _enhanceManualRedaction(addRedactionRequest: IAddRedactionRequest) { - const legalOption: LegalBasisOption = this.redactionForm.get('reason').value; - addRedactionRequest.type = this.redactionForm.get('dictionary').value; + const legalOption: LegalBasisOption = this.form.get('reason').value; + addRedactionRequest.type = this.form.get('dictionary').value; if (legalOption) { addRedactionRequest.reason = legalOption.description; addRedactionRequest.legalBasis = legalOption.legalBasis; } - addRedactionRequest.addToDictionary = this.isDictionaryRequest; // todo fix this in backend + addRedactionRequest.addToDictionary = this.isDictionaryRequest && addRedactionRequest.type !== 'dossier_redaction'; + addRedactionRequest.addToDossierDictionary = this.isDictionaryRequest && addRedactionRequest.type === 'dossier_redaction'; + if (!addRedactionRequest.reason) { addRedactionRequest.reason = 'Dictionary Request'; } - const commentValue = this.redactionForm.get('comment').value; + const commentValue = this.form.get('comment').value; addRedactionRequest.comment = commentValue ? { text: commentValue } : null; - addRedactionRequest.section = this.redactionForm.get('section').value; - addRedactionRequest.value = addRedactionRequest.rectangle - ? this.redactionForm.get('classification').value - : addRedactionRequest.value; + addRedactionRequest.section = this.form.get('section').value; + addRedactionRequest.value = addRedactionRequest.rectangle ? this.form.get('classification').value : addRedactionRequest.value; + } + + get disabled() { + return this.form.invalid; } } diff --git a/apps/red-ui/src/app/modules/dossier/dialogs/recategorize-image-dialog/recategorize-image-dialog.component.html b/apps/red-ui/src/app/modules/dossier/dialogs/recategorize-image-dialog/recategorize-image-dialog.component.html index aeafba369..a1073bcd3 100644 --- a/apps/red-ui/src/app/modules/dossier/dialogs/recategorize-image-dialog/recategorize-image-dialog.component.html +++ b/apps/red-ui/src/app/modules/dossier/dialogs/recategorize-image-dialog/recategorize-image-dialog.component.html @@ -1,5 +1,5 @@
-
+
@@ -23,12 +23,12 @@
-
- +
diff --git a/apps/red-ui/src/app/modules/dossier/dialogs/recategorize-image-dialog/recategorize-image-dialog.component.ts b/apps/red-ui/src/app/modules/dossier/dialogs/recategorize-image-dialog/recategorize-image-dialog.component.ts index 3e6771b28..488438e46 100644 --- a/apps/red-ui/src/app/modules/dossier/dialogs/recategorize-image-dialog/recategorize-image-dialog.component.ts +++ b/apps/red-ui/src/app/modules/dossier/dialogs/recategorize-image-dialog/recategorize-image-dialog.component.ts @@ -1,17 +1,17 @@ -import { Component, Inject, OnInit } from '@angular/core'; -import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { Component, Inject, Injector, OnInit } from '@angular/core'; +import { FormBuilder, Validators } from '@angular/forms'; import { PermissionsService } from '@services/permissions.service'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { AnnotationWrapper } from '@models/file/annotation.wrapper'; import { imageCategoriesTranslations } from '../../translations/image-categories-translations'; import { ImageCategory } from '../../models/image-category.model'; import { Dossier } from '@red/domain'; +import { BaseDialogComponent } from '@iqser/common-ui'; @Component({ templateUrl: './recategorize-image-dialog.component.html', }) -export class RecategorizeImageDialogComponent implements OnInit { - recategorizeImageForm: FormGroup; +export class RecategorizeImageDialogComponent extends BaseDialogComponent implements OnInit { isDocumentAdmin: boolean; typeOptions: ImageCategory[] = ['signature', 'logo', 'formula', 'image']; translations = imageCategoriesTranslations; @@ -19,27 +19,32 @@ export class RecategorizeImageDialogComponent implements OnInit { constructor( private readonly _permissionsService: PermissionsService, private readonly _formBuilder: FormBuilder, - public dialogRef: MatDialogRef, + protected readonly _injector: Injector, + protected readonly _dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data: { annotations: AnnotationWrapper[]; dossier: Dossier }, - ) {} + ) { + super(_injector, _dialogRef); + } get changed(): boolean { - return this.recategorizeImageForm.get('type').value !== this.data.annotations[0].type; + return this.form.get('type').value !== this.data.annotations[0].type; } ngOnInit() { + super.ngOnInit(); this.isDocumentAdmin = this._permissionsService.isApprover(this.data.dossier); - this.recategorizeImageForm = this._formBuilder.group({ + this.form = this._formBuilder.group({ type: [this.data.annotations[0].type, Validators.required], comment: this.isDocumentAdmin ? [null] : [null, Validators.required], }); + this.initialFormValue = this.form.getRawValue(); } save() { - this.dialogRef.close({ - type: this.recategorizeImageForm.get('type').value, - comment: this.recategorizeImageForm.get('comment').value, + this._dialogRef.close({ + type: this.form.get('type').value, + comment: this.form.get('comment').value, }); } } diff --git a/apps/red-ui/src/app/modules/dossier/dialogs/remove-annotations-dialog/remove-annotations-dialog.component.html b/apps/red-ui/src/app/modules/dossier/dialogs/remove-annotations-dialog/remove-annotations-dialog.component.html index 9f40bf629..287629e70 100644 --- a/apps/red-ui/src/app/modules/dossier/dialogs/remove-annotations-dialog/remove-annotations-dialog.component.html +++ b/apps/red-ui/src/app/modules/dossier/dialogs/remove-annotations-dialog/remove-annotations-dialog.component.html @@ -7,7 +7,7 @@ ) | translate: { hint: data.hint } }}
-
+
{{ (data.removeFromDictionary @@ -46,12 +46,12 @@
-
- + diff --git a/apps/red-ui/src/app/modules/dossier/dialogs/remove-annotations-dialog/remove-annotations-dialog.component.ts b/apps/red-ui/src/app/modules/dossier/dialogs/remove-annotations-dialog/remove-annotations-dialog.component.ts index f6ecb7ab3..16591d344 100644 --- a/apps/red-ui/src/app/modules/dossier/dialogs/remove-annotations-dialog/remove-annotations-dialog.component.ts +++ b/apps/red-ui/src/app/modules/dossier/dialogs/remove-annotations-dialog/remove-annotations-dialog.component.ts @@ -1,10 +1,10 @@ -import { Component, Inject } from '@angular/core'; +import { Component, Inject, Injector } from '@angular/core'; import { AnnotationWrapper } from '@models/file/annotation.wrapper'; import { TranslateService } from '@ngx-translate/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { PermissionsService } from '@services/permissions.service'; -import { FormBuilder, FormGroup, Validators } from '@angular/forms'; -import { humanize } from '@iqser/common-ui'; +import { FormBuilder, Validators } from '@angular/forms'; +import { BaseDialogComponent, humanize } from '@iqser/common-ui'; import { Dossier } from '@red/domain'; export interface RemoveAnnotationsDialogInput { @@ -18,23 +18,24 @@ export interface RemoveAnnotationsDialogInput { templateUrl: './remove-annotations-dialog.component.html', styleUrls: ['./remove-annotations-dialog.component.scss'], }) -export class RemoveAnnotationsDialogComponent { - redactionForm: FormGroup; - +export class RemoveAnnotationsDialogComponent extends BaseDialogComponent { constructor( private readonly _translateService: TranslateService, private readonly _formBuilder: FormBuilder, readonly permissionsService: PermissionsService, - public dialogRef: MatDialogRef, + protected readonly _injector: Injector, + protected readonly _dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data: RemoveAnnotationsDialogInput, ) { - this.redactionForm = this._formBuilder.group({ + super(_injector, _dialogRef); + this.form = this._formBuilder.group({ comment: this.permissionsService.isApprover(this.data.dossier) ? [null] : [null, Validators.required], }); + this.initialFormValue = this.form.getRawValue(); } - confirm() { - this.dialogRef.close({ comment: this.redactionForm.getRawValue().comment }); + save() { + this._dialogRef.close({ comment: this.form.getRawValue().comment }); } printable(annotation: AnnotationWrapper) { diff --git a/apps/red-ui/src/app/modules/dossier/dialogs/resize-annotation-dialog/resize-annotation-dialog.component.html b/apps/red-ui/src/app/modules/dossier/dialogs/resize-annotation-dialog/resize-annotation-dialog.component.html index 72a505f70..a83614b35 100644 --- a/apps/red-ui/src/app/modules/dossier/dialogs/resize-annotation-dialog/resize-annotation-dialog.component.html +++ b/apps/red-ui/src/app/modules/dossier/dialogs/resize-annotation-dialog/resize-annotation-dialog.component.html @@ -1,5 +1,5 @@
-
+
@@ -10,12 +10,12 @@
-
- +
diff --git a/apps/red-ui/src/app/modules/dossier/dialogs/resize-annotation-dialog/resize-annotation-dialog.component.ts b/apps/red-ui/src/app/modules/dossier/dialogs/resize-annotation-dialog/resize-annotation-dialog.component.ts index 51e55a1ef..14b6e1b64 100644 --- a/apps/red-ui/src/app/modules/dossier/dialogs/resize-annotation-dialog/resize-annotation-dialog.component.ts +++ b/apps/red-ui/src/app/modules/dossier/dialogs/resize-annotation-dialog/resize-annotation-dialog.component.ts @@ -1,34 +1,40 @@ -import { Component, Inject, OnInit } from '@angular/core'; +import { Component, Inject, Injector, OnInit } from '@angular/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; -import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { FormBuilder, Validators } from '@angular/forms'; import { PermissionsService } from '@services/permissions.service'; import { Dossier } from '@red/domain'; +import { BaseDialogComponent } from '@iqser/common-ui'; @Component({ templateUrl: './resize-annotation-dialog.component.html', }) -export class ResizeAnnotationDialogComponent implements OnInit { - resizeForm: FormGroup; +export class ResizeAnnotationDialogComponent extends BaseDialogComponent implements OnInit { isDocumentAdmin: boolean; constructor( private readonly _permissionsService: PermissionsService, private readonly _formBuilder: FormBuilder, - public dialogRef: MatDialogRef, + protected readonly _injector: Injector, + protected readonly _dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) private readonly _data: { dossier: Dossier }, - ) {} + ) { + super(_injector, _dialogRef); + } ngOnInit() { + super.ngOnInit(); + this.isDocumentAdmin = this._permissionsService.isApprover(this._data.dossier); - this.resizeForm = this._formBuilder.group({ + this.form = this._formBuilder.group({ comment: this.isDocumentAdmin ? [null] : [null, Validators.required], }); + this.initialFormValue = this.form.getRawValue(); } save() { - this.dialogRef.close({ - comment: this.resizeForm.get('comment').value, + this._dialogRef.close({ + comment: this.form.get('comment').value, }); } } diff --git a/apps/red-ui/src/app/modules/dossier/dossiers.module.ts b/apps/red-ui/src/app/modules/dossier/dossiers.module.ts index 495eb2a96..9fd58b8cb 100644 --- a/apps/red-ui/src/app/modules/dossier/dossiers.module.ts +++ b/apps/red-ui/src/app/modules/dossier/dossiers.module.ts @@ -10,10 +10,7 @@ import { SharedModule } from '@shared/shared.module'; import { DossiersRoutingModule } from './dossiers-routing.module'; import { FileUploadDownloadModule } from '@upload-download/file-upload-download.module'; import { DossiersDialogService } from './services/dossiers-dialog.service'; -import { AnnotationActionsService } from './services/annotation-actions.service'; -import { PdfViewerDataService } from './services/pdf-viewer-data.service'; import { ManualAnnotationService } from './services/manual-annotation.service'; -import { AnnotationDrawService } from './services/annotation-draw.service'; import { AnnotationProcessingService } from './services/annotation-processing.service'; import { EditDossierDialogComponent } from './dialogs/edit-dossier-dialog/edit-dossier-dialog.component'; import { EditDossierGeneralInfoComponent } from './dialogs/edit-dossier-dialog/general-info/edit-dossier-general-info.component'; @@ -56,14 +53,7 @@ const components = [ ...dialogs, ]; -const services = [ - DossiersDialogService, - AnnotationActionsService, - ManualAnnotationService, - PdfViewerDataService, - AnnotationDrawService, - AnnotationProcessingService, -]; +const services = [DossiersDialogService, ManualAnnotationService, AnnotationProcessingService]; @NgModule({ declarations: [...components], diff --git a/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/components/screen-header/screen-header.component.html b/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/components/screen-header/screen-header.component.html index 0af735eb1..279dc475f 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/components/screen-header/screen-header.component.html +++ b/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/components/screen-header/screen-header.component.html @@ -5,10 +5,15 @@ [showCloseButton]="true" [viewModeSelection]="viewModeSelection" > - + - + diff --git a/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/components/table-item/file-workload/file-workload.component.ts b/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/components/table-item/file-workload/file-workload.component.ts index 0b69ee12a..cd7ceaddc 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/components/table-item/file-workload/file-workload.component.ts +++ b/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/components/table-item/file-workload/file-workload.component.ts @@ -2,6 +2,7 @@ import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; import { AppStateService } from '@state/app-state.service'; import { File } from '@red/domain'; import { DossiersService } from '@services/entity-services/dossiers.service'; +import { UserService } from '../../../../../../../services/user.service'; @Component({ selector: 'redaction-file-workload', @@ -12,7 +13,11 @@ import { DossiersService } from '@services/entity-services/dossiers.service'; export class FileWorkloadComponent { @Input() file: File; - constructor(private readonly _appStateService: AppStateService, private readonly _dossiersService: DossiersService) {} + constructor( + public readonly userService: UserService, + private readonly _appStateService: AppStateService, + private readonly _dossiersService: DossiersService, + ) {} get suggestionColor() { return this._getDictionaryColor('suggestion'); diff --git a/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/config.service.ts b/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/config.service.ts index 77a6b107e..21df3658c 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/config.service.ts +++ b/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/config.service.ts @@ -102,7 +102,7 @@ export class ConfigService { }; } - actionConfig(dossierId: string): List { + actionConfig(dossierId: string, disabled$: Observable): List { return [ { label: this._translateService.instant('dossier-overview.header-actions.edit'), @@ -110,6 +110,7 @@ export class ConfigService { icon: 'iqser:edit', hide: !this._userService.currentUser.isManager, helpModeKey: 'edit-dossier-attributes', + disabled$, }, ]; } diff --git a/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/screen/dossier-overview-screen.component.ts b/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/screen/dossier-overview-screen.component.ts index bf8865bdd..ccf776a8f 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/screen/dossier-overview-screen.component.ts +++ b/apps/red-ui/src/app/modules/dossier/screens/dossier-overview/screen/dossier-overview-screen.component.ts @@ -15,19 +15,19 @@ import { FileDropOverlayService } from '@upload-download/services/file-drop-over import { FileUploadModel } from '@upload-download/model/file-upload.model'; import { FileUploadService } from '@upload-download/services/file-upload.service'; import { StatusOverlayService } from '@upload-download/services/status-overlay.service'; -import * as moment from 'moment'; -import { Observable, timer } from 'rxjs'; +import { Observable } from 'rxjs'; import { filter, skip, switchMap, tap } from 'rxjs/operators'; import { convertFiles, Files, handleFileDrop } from '@utils/index'; import { CircleButtonTypes, + CustomError, DefaultListingServices, + ErrorService, ListingComponent, ListingModes, LoadingService, NestedFilter, OnAttach, - OnDetach, TableColumnConfig, TableComponent, WorkflowConfig, @@ -37,7 +37,6 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { PermissionsService } from '@services/permissions.service'; import { ActivatedRoute, Router } from '@angular/router'; import { FileAttributesService } from '@services/entity-services/file-attributes.service'; -import { ConfigService as AppConfigService } from '@services/config.service'; import { ConfigService } from '../config.service'; import { DossiersService } from '@services/entity-services/dossiers.service'; import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service'; @@ -45,7 +44,6 @@ import { LongPressEvent } from '@shared/directives/long-press.directive'; import { UserPreferenceService } from '@services/user-preference.service'; import { FilesMapService } from '@services/entity-services/files-map.service'; import { FilesService } from '@services/entity-services/files.service'; -import { CHANGED_CHECK_INTERVAL } from '@utils/constants'; @Component({ templateUrl: './dossier-overview-screen.component.html', @@ -53,7 +51,7 @@ import { CHANGED_CHECK_INTERVAL } from '@utils/constants'; providers: [...DefaultListingServices, { provide: ListingComponent, useExisting: forwardRef(() => DossierOverviewScreenComponent) }], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class DossierOverviewScreenComponent extends ListingComponent implements OnInit, OnDestroy, OnDetach, OnAttach { +export class DossierOverviewScreenComponent extends ListingComponent implements OnInit, OnDestroy, OnAttach { readonly listingModes = ListingModes; readonly circleButtonTypes = CircleButtonTypes; readonly tableHeaderLabel = _('dossier-overview.table-header.title'); @@ -78,21 +76,21 @@ export class DossierOverviewScreenComponent extends ListingComponent imple constructor( protected readonly _injector: Injector, private readonly _router: Router, - readonly permissionsService: PermissionsService, private readonly _loadingService: LoadingService, private readonly _dossiersService: DossiersService, private readonly _dossierTemplatesService: DossierTemplatesService, - private readonly _appConfigService: AppConfigService, private readonly _fileUploadService: FileUploadService, private readonly _filesService: FilesService, private readonly _statusOverlayService: StatusOverlayService, private readonly _fileDropOverlayService: FileDropOverlayService, private readonly _dossierAttributesService: DossierAttributesService, private readonly _fileAttributesService: FileAttributesService, - readonly configService: ConfigService, private readonly _userPreferenceService: UserPreferenceService, private readonly _fileMapService: FilesMapService, - activatedRoute: ActivatedRoute, + private readonly _errorService: ErrorService, + readonly permissionsService: PermissionsService, + readonly configService: ConfigService, + readonly activatedRoute: ActivatedRoute, ) { super(_injector); this.dossierId = activatedRoute.snapshot.paramMap.get('dossierId'); @@ -118,6 +116,8 @@ export class DossierOverviewScreenComponent extends ListingComponent imple async ngOnInit(): Promise { this._loadEntitiesFromState(); + this._setRemovableSubscriptions(); + this.addSubscription = this._fileMapService .get$(this.dossierId) .pipe( @@ -128,18 +128,17 @@ export class DossierOverviewScreenComponent extends ListingComponent imple this._fileDropOverlayService.initFileDropHandling(this.dossierId); - this.addSubscription = timer(CHANGED_CHECK_INTERVAL, CHANGED_CHECK_INTERVAL) - .pipe( - switchMap(() => this._filesService.hasChanges$(this.dossierId)), - filter(changed => changed), - switchMap(() => this._reloadFiles()), - ) - .subscribe(); - this.addSubscription = this.configService.listingMode$.subscribe(() => { this._computeAllFilters(); }); + this.addSubscription = this._dossiersService.dossierFileChanges$ + .pipe( + filter(dossierId => dossierId === this.dossierId), + switchMap(dossierId => this._filesService.loadAll(dossierId)), + ) + .subscribe(); + this.addSubscription = this._dossierTemplatesService .getEntityChanged$(this.currentDossier.dossierTemplateId) .pipe( @@ -166,11 +165,10 @@ export class DossierOverviewScreenComponent extends ListingComponent imple ngOnAttach() { this._fileDropOverlayService.initFileDropHandling(this.dossierId); + this._setRemovableSubscriptions(); this._tableComponent?.scrollToLastIndex(); } - ngOnDetach() {} - forceReanalysisAction($event: LongPressEvent) { this.analysisForced = !$event.touchEnd && this._userPreferenceService.areDevFeaturesEnabled; } @@ -192,8 +190,18 @@ export class DossierOverviewScreenComponent extends ListingComponent imple (this._fileInput as any).nativeElement.value = null; } - recentlyModifiedChecker = (file: File) => - moment(file.lastUpdated).add(this._appConfigService.values.RECENT_PERIOD_IN_HOURS, 'hours').isAfter(moment()); + private _setRemovableSubscriptions(): void { + this.addActiveScreenSubscription = this._dossiersService + .getEntityDeleted$(this.dossierId) + .pipe(tap(() => this._handleDeletedDossier())) + .subscribe(); + } + + private _handleDeletedDossier(): void { + this._errorService.set( + new CustomError(_('error.deleted-entity.dossier.label'), _('error.deleted-entity.dossier.action'), 'iqser:expand'), + ); + } private _updateFileAttributes(): void { this._fileAttributeConfigs = @@ -204,11 +212,6 @@ export class DossierOverviewScreenComponent extends ListingComponent imple this._computeAllFilters(); } - private async _reloadFiles() { - await this._filesService.loadAll(this.dossierId).toPromise(); - this._computeAllFilters(); - } - private _loadEntitiesFromState() { this.currentDossier = this._dossiersService.find(this.dossierId); this._computeAllFilters(); diff --git a/apps/red-ui/src/app/modules/dossier/screens/dossiers-listing/components/dossiers-listing-details/dossiers-listing-details.component.ts b/apps/red-ui/src/app/modules/dossier/screens/dossiers-listing/components/dossiers-listing-details/dossiers-listing-details.component.ts index cb44b7a4c..68f053b36 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/dossiers-listing/components/dossiers-listing-details/dossiers-listing-details.component.ts +++ b/apps/red-ui/src/app/modules/dossier/screens/dossiers-listing/components/dossiers-listing-details/dossiers-listing-details.component.ts @@ -38,10 +38,10 @@ export class DossiersListingDetailsComponent { private async _toDossierChartData(dossiers: Dossier[]): Promise { // TODO: deleted dossiers count should come with stats - const deletedDossiers = await this.dossiersService.getDeleted(); + // const deletedDossiers = await this.dossiersService.getDeleted(); return [ { value: dossiers.length, color: 'ACTIVE', label: _('active') }, - { value: deletedDossiers.length, color: 'DELETED', label: _('archived') }, + // { value: deletedDossiers.length, color: 'DELETED', label: _('archived') }, ]; } diff --git a/apps/red-ui/src/app/modules/dossier/screens/dossiers-listing/screen/dossiers-listing-screen.component.ts b/apps/red-ui/src/app/modules/dossier/screens/dossiers-listing/screen/dossiers-listing-screen.component.ts index c7ba3f10b..d70a2ae00 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/dossiers-listing/screen/dossiers-listing-screen.component.ts +++ b/apps/red-ui/src/app/modules/dossier/screens/dossiers-listing/screen/dossiers-listing-screen.component.ts @@ -1,19 +1,17 @@ -import { ChangeDetectionStrategy, Component, forwardRef, Injector, OnDestroy, OnInit, TemplateRef, ViewChild } from '@angular/core'; +import { ChangeDetectionStrategy, Component, forwardRef, Injector, OnInit, TemplateRef, ViewChild } from '@angular/core'; import { Dossier } from '@red/domain'; import { UserService } from '@services/user.service'; import { PermissionsService } from '@services/permissions.service'; import { TranslateChartService } from '@services/translate-chart.service'; -import { timer } from 'rxjs'; import { Router } from '@angular/router'; import { DossiersDialogService } from '../../../services/dossiers-dialog.service'; -import { DefaultListingServicesTmp, EntitiesService, ListingComponent, OnAttach, OnDetach, TableComponent } from '@iqser/common-ui'; +import { DefaultListingServicesTmp, EntitiesService, ListingComponent, OnAttach, TableComponent } from '@iqser/common-ui'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { ConfigService } from '../config.service'; import { DossiersService } from '@services/entity-services/dossiers.service'; import { FilesService } from '@services/entity-services/files.service'; import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service'; -import { switchMap, tap } from 'rxjs/operators'; -import { CHANGED_CHECK_INTERVAL } from '@utils/constants'; +import { tap } from 'rxjs/operators'; @Component({ templateUrl: './dossiers-listing-screen.component.html', @@ -25,7 +23,7 @@ import { CHANGED_CHECK_INTERVAL } from '@utils/constants'; ], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class DossiersListingScreenComponent extends ListingComponent implements OnInit, OnDestroy, OnAttach, OnDetach { +export class DossiersListingScreenComponent extends ListingComponent implements OnInit, OnAttach { readonly currentUser = this._userService.currentUser; readonly tableColumnConfigs = this._configService.tableConfig; readonly tableHeaderLabel = _('dossier-listing.table-header.title'); @@ -57,22 +55,13 @@ export class DossiersListingScreenComponent extends ListingComponent im } ngOnInit(): void { - this.addSubscription = timer(CHANGED_CHECK_INTERVAL, CHANGED_CHECK_INTERVAL) - .pipe(switchMap(() => this._dossiersService.loadAllIfChanged())) - .subscribe(); - this.addSubscription = this._dossiersService.all$.pipe(tap(() => this._computeAllFilters())).subscribe(); } ngOnAttach(): void { - this.ngOnInit(); this._tableComponent?.scrollToLastIndex(); } - ngOnDetach(): void { - this.ngOnDestroy(); - } - openAddDossierDialog(): void { this._dialogService.openDialog('addDossier', null, null, async (addResponse: { dossier: Dossier; addMembers: boolean }) => { await this._router.navigate([addResponse.dossier.routerLink]); diff --git a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/annotation-actions/annotation-actions.component.ts b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/annotation-actions/annotation-actions.component.ts index e7f96a6e6..2dfa0851f 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/annotation-actions/annotation-actions.component.ts +++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/annotation-actions/annotation-actions.component.ts @@ -2,7 +2,7 @@ import { Component, EventEmitter, Input, OnChanges, Output } from '@angular/core import { AnnotationWrapper } from '@models/file/annotation.wrapper'; import { PermissionsService } from '@services/permissions.service'; import { AnnotationPermissions } from '@models/file/annotation.permissions'; -import { AnnotationActionsService } from '../../../../services/annotation-actions.service'; +import { AnnotationActionsService } from '../../services/annotation-actions.service'; import { WebViewerInstance } from '@pdftron/webviewer'; import { UserService } from '@services/user.service'; import { Dossier, File } from '@red/domain'; diff --git a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/comments/comments.component.html b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/comments/comments.component.html index 8686a4b2e..427cebe5c 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/comments/comments.component.html +++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/comments/comments.component.html @@ -8,7 +8,7 @@
; - @HostBinding('class.hidden') private _hidden = true; + @HostBinding('class.hidden') _hidden = true; @ViewChild(InputWithActionComponent) private readonly _input: InputWithActionComponent; private readonly _fileId: string; private readonly _dossierId: string; @@ -28,49 +30,59 @@ export class CommentsComponent { readonly permissionsService: PermissionsService, private readonly _userService: UserService, private readonly _manualAnnotationService: ManualAnnotationService, - private readonly _changeDetectorRef: ChangeDetectorRef, + private readonly _commentingService: CommentingService, + private readonly _loadingService: LoadingService, + private readonly _changeRef: ChangeDetectorRef, readonly filesMapService: FilesMapService, activatedRoute: ActivatedRoute, ) { + super(); this._fileId = activatedRoute.snapshot.paramMap.get('fileId'); this._dossierId = activatedRoute.snapshot.paramMap.get('dossierId'); this.file$ = filesMapService.watch$(this._dossierId, this._fileId); } - addComment(value: string): void { + ngOnChanges() { + this.addSubscription = this._commentingService + .isActive$(this.annotation.id) + .pipe( + tap(active => { + this._hidden = !active; + }), + ) + .subscribe(); + } + + async addComment(value: string): Promise { if (!value) { return; } - this._manualAnnotationService + this._loadingService.start(); + const commentId = await this._manualAnnotationService .addComment(value, this.annotation.id, this._dossierId, this._fileId) - .toPromise() - .then(commentId => { - this.annotation.comments.push({ - text: value, - id: commentId, - annotationId: this.annotation.id, - user: this._userService.currentUser.id, - }); - this._input.reset(); - this._changeDetectorRef.markForCheck(); - }); + .toPromise(); + this.annotation.comments.push({ + text: value, + id: commentId, + annotationId: this.annotation.id, + user: this._userService.currentUser.id, + }); + this._input.reset(); + this._changeRef.markForCheck(); + this._loadingService.stop(); } toggleExpandComments($event?: MouseEvent): void { $event?.stopPropagation(); - this._hidden = !this._hidden; + this._commentingService.toggle(this.annotation.id); } - deleteComment(comment: IComment): void { - this._manualAnnotationService - .deleteComment(comment.id, this.annotation.id, this._dossierId, this._fileId) - .toPromise() - .then(() => { - this.annotation.comments.splice(this.annotation.comments.indexOf(comment), 1); - if (!this.annotation.comments.length) { - this._hidden = true; - } - this._changeDetectorRef.markForCheck(); - }); + async deleteComment($event: MouseEvent, comment: IComment): Promise { + $event.stopPropagation(); + this._loadingService.start(); + await this._manualAnnotationService.deleteComment(comment.id, this.annotation.id, this._dossierId, this._fileId).toPromise(); + this.annotation.comments.splice(this.annotation.comments.indexOf(comment), 1); + this._changeRef.markForCheck(); + this._loadingService.stop(); } } diff --git a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/file-workload/file-workload.component.html b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/file-workload/file-workload.component.html index 7111f4d66..71c41366d 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/file-workload/file-workload.component.html +++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/file-workload/file-workload.component.html @@ -38,13 +38,6 @@
-
-
- - -
-
-
@@ -224,9 +216,9 @@ diff --git a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/file-workload/file-workload.component.ts b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/file-workload/file-workload.component.ts index 8f7c86e16..584a25e84 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/file-workload/file-workload.component.ts +++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/file-workload/file-workload.component.ts @@ -28,10 +28,11 @@ import { PermissionsService } from '@services/permissions.service'; import { WebViewerInstance } from '@pdftron/webviewer'; import { BehaviorSubject, combineLatest, Observable } from 'rxjs'; import { map, tap } from 'rxjs/operators'; -import { File, IViewedPage } from '@red/domain'; +import { File } from '@red/domain'; import { ExcludedPagesService } from '../../services/excluded-pages.service'; import { MultiSelectService } from '../../services/multi-select.service'; import { DocumentInfoService } from '../../services/document-info.service'; +import { SkippedService } from '../../services/skipped.service'; const COMMAND_KEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Escape']; const ALL_HOTKEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown']; @@ -51,16 +52,13 @@ export class FileWorkloadComponent { @Input() activeViewerPage: number; @Input() shouldDeselectAnnotationsOnPageChange: boolean; @Input() dialogRef: MatDialogRef; - @Input() viewedPages: IViewedPage[]; @Input() file!: File; - @Input() hideSkipped: boolean; @Input() annotationActionsTemplate: TemplateRef; @Input() viewer: WebViewerInstance; @Output() readonly shouldDeselectAnnotationsOnPageChangeChange = new EventEmitter(); @Output() readonly selectAnnotations = new EventEmitter(); @Output() readonly deselectAnnotations = new EventEmitter(); @Output() readonly selectPage = new EventEmitter(); - @Output() readonly toggleSkipped = new EventEmitter(); @Output() readonly annotationsChanged = new EventEmitter(); displayedPages: number[] = []; pagesPanelActive = true; @@ -76,6 +74,7 @@ export class FileWorkloadComponent { readonly excludedPagesService: ExcludedPagesService, readonly multiSelectService: MultiSelectService, readonly documentInfoService: DocumentInfoService, + readonly skippedService: SkippedService, private readonly _permissionsService: PermissionsService, private readonly _changeDetectorRef: ChangeDetectorRef, private readonly _filterService: FilterService, @@ -225,7 +224,7 @@ export class FileWorkloadComponent { scrollAnnotationsToPage(page: number, mode: 'always' | 'if-needed' = 'if-needed'): void { if (this._annotationsElement) { - const elements = this._annotationsElement.nativeElement.querySelectorAll(`div[anotation-page-header="${page}"]`); + const elements: HTMLElement[] = this._annotationsElement.nativeElement.querySelectorAll(`div[anotation-page-header="${page}"]`); FileWorkloadComponent._scrollToFirstElement(elements, mode); } } @@ -235,7 +234,7 @@ export class FileWorkloadComponent { if (!this.selectedAnnotations || this.selectedAnnotations.length === 0 || !this._annotationsElement) { return; } - const elements = this._annotationsElement.nativeElement.querySelectorAll( + const elements: HTMLElement[] = this._annotationsElement.nativeElement.querySelectorAll( `div[annotation-id="${this._firstSelectedAnnotation?.id}"].active`, ); FileWorkloadComponent._scrollToFirstElement(elements); @@ -412,7 +411,7 @@ export class FileWorkloadComponent { private _scrollQuickNavigationToPage(page: number) { if (this._quickNavigationElement) { - const elements = this._quickNavigationElement.nativeElement.querySelectorAll(`#quick-nav-page-${page}`); + const elements: HTMLElement[] = this._quickNavigationElement.nativeElement.querySelectorAll(`#quick-nav-page-${page}`); FileWorkloadComponent._scrollToFirstElement(elements); } } diff --git a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/page-indicator/page-indicator.component.ts b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/page-indicator/page-indicator.component.ts index 922dd5516..785a6153f 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/page-indicator/page-indicator.component.ts +++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/page-indicator/page-indicator.component.ts @@ -6,6 +6,7 @@ import { ViewedPagesService } from '@services/entity-services/viewed-pages.servi import { File, IViewedPage } from '@red/domain'; import { AutoUnsubscribe } from '@iqser/common-ui'; import { FilesMapService } from '@services/entity-services/files-map.service'; +import { FilePreviewStateService } from '../../services/file-preview-state.service'; @Component({ selector: 'redaction-page-indicator', @@ -18,7 +19,6 @@ export class PageIndicatorComponent extends AutoUnsubscribe implements OnDestroy @Input() active = false; @Input() showDottedIcon = false; @Input() number: number; - @Input() viewedPages: IViewedPage[]; @Input() activeSelection = false; @Output() readonly pageSelected = new EventEmitter(); @@ -33,25 +33,17 @@ export class PageIndicatorComponent extends AutoUnsubscribe implements OnDestroy private readonly _configService: ConfigService, private readonly _changeDetectorRef: ChangeDetectorRef, private readonly _permissionService: PermissionsService, + private readonly _stateService: FilePreviewStateService, ) { super(); } get activePage() { - return this.viewedPages?.find(p => p.page === this.number); + return this._viewedPages.find(p => p.page === this.number); } - private _setReadState() { - const readBefore = this.read; - const activePage = this.activePage; - if (!activePage) { - this.read = false; - } else { - // console.log('setting read to',activePage.showAsUnseen, !activePage.showAsUnseen); - this.read = !activePage.showAsUnseen; - } - // console.log(this.number, readBefore, activePage, this.read); - this._changeDetectorRef.detectChanges(); + private get _viewedPages(): IViewedPage[] { + return this._stateService.fileData?.viewedPages || []; } ngOnChanges() { @@ -87,20 +79,33 @@ export class PageIndicatorComponent extends AutoUnsubscribe implements OnDestroy } } + private _setReadState() { + const readBefore = this.read; + const activePage = this.activePage; + if (!activePage) { + this.read = false; + } else { + // console.log('setting read to',activePage.showAsUnseen, !activePage.showAsUnseen); + this.read = !activePage.showAsUnseen; + } + // console.log(this.number, readBefore, activePage, this.read); + this._changeDetectorRef.detectChanges(); + } + private async _markPageRead() { await this._viewedPagesService.addPage({ page: this.number }, this.file.dossierId, this.file.fileId).toPromise(); if (this.activePage) { this.activePage.showAsUnseen = false; } else { - this.viewedPages?.push({ page: this.number, fileId: this.file.fileId }); + this._viewedPages.push({ page: this.number, fileId: this.file.fileId }); } this._setReadState(); } private async _markPageUnread() { await this._viewedPagesService.removePage(this.file.dossierId, this.file.fileId, this.number).toPromise(); - this.viewedPages?.splice( - this.viewedPages?.findIndex(p => p.page === this.number), + this._viewedPages.splice( + this._viewedPages.findIndex(p => p.page === this.number), 1, ); this._setReadState(); diff --git a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/pdf-viewer/pdf-viewer.component.ts b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/pdf-viewer/pdf-viewer.component.ts index cbd7d8ccc..f0c0968a4 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/pdf-viewer/pdf-viewer.component.ts +++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/pdf-viewer/pdf-viewer.component.ts @@ -22,12 +22,12 @@ import { import { AnnotationWrapper } from '@models/file/annotation.wrapper'; import { ManualAnnotationService } from '../../../../services/manual-annotation.service'; import { environment } from '@environments/environment'; -import { AnnotationDrawService } from '../../../../services/annotation-draw.service'; -import { AnnotationActionsService } from '../../../../services/annotation-actions.service'; +import { AnnotationDrawService } from '../../services/annotation-draw.service'; +import { AnnotationActionsService } from '../../services/annotation-actions.service'; import { UserPreferenceService } from '@services/user-preference.service'; import { BASE_HREF } from '../../../../../../tokens'; import { ConfigService } from '@services/config.service'; -import { ConfirmationDialogInput, LoadingService } from '@iqser/common-ui'; +import { AutoUnsubscribe, ConfirmationDialogInput, LoadingService, shareDistinctLast } from '@iqser/common-ui'; import { DossiersDialogService } from '../../../../services/dossiers-dialog.service'; import { loadCompareDocumentWrapper } from '../../../../utils/compare-mode.utils'; import { PdfViewerUtils } from '../../../../utils/pdf-viewer.utils'; @@ -36,11 +36,13 @@ import { ActivatedRoute } from '@angular/router'; import { toPosition } from '../../../../utils/pdf-calculation.utils'; import { ViewModeService } from '../../services/view-mode.service'; import { MultiSelectService } from '../../services/multi-select.service'; +import { FilePreviewStateService } from '../../services/file-preview-state.service'; +import { filter, switchMap, tap } from 'rxjs/operators'; import Tools = Core.Tools; import TextTool = Tools.TextTool; import Annotation = Core.Annotations.Annotation; -const ALLOWED_KEYBOARD_SHORTCUTS = ['+', '-', 'p', 'r', 'Escape'] as const; +const ALLOWED_KEYBOARD_SHORTCUTS: readonly string[] = ['+', '-', 'p', 'r', 'Escape'] as const; const dataElements = { ADD_REDACTION: 'add-redaction', ADD_DICTIONARY: 'add-dictionary', @@ -61,8 +63,7 @@ const dataElements = { templateUrl: './pdf-viewer.component.html', styleUrls: ['./pdf-viewer.component.scss'], }) -export class PdfViewerComponent implements OnInit, OnChanges { - @Input() fileData: Blob; +export class PdfViewerComponent extends AutoUnsubscribe implements OnInit, OnChanges { @Input() file: File; @Input() dossier: Dossier; @Input() canPerformActions = false; @@ -95,9 +96,12 @@ export class PdfViewerComponent implements OnInit, OnChanges { private readonly _annotationActionsService: AnnotationActionsService, private readonly _configService: ConfigService, private readonly _loadingService: LoadingService, + private readonly _stateService: FilePreviewStateService, readonly viewModeService: ViewModeService, readonly multiSelectService: MultiSelectService, - ) {} + ) { + super(); + } private get _toggleTooltipsBtnTitle(): string { return this._translateService.instant(_('pdf-viewer.toggle-tooltips'), { @@ -105,9 +109,27 @@ export class PdfViewerComponent implements OnInit, OnChanges { }); } + private get _toggleTooltipsIcon(): string { + return this._convertPath( + this._userPreferenceService.getFilePreviewTooltipsPreference() + ? '/assets/icons/general/pdftron-action-enable-tooltips.svg' + : '/assets/icons/general/pdftron-action-disable-tooltips.svg', + ); + } + async ngOnInit() { this._setReadyAndInitialState = this._setReadyAndInitialState.bind(this); - await this.loadViewer(); + await this._loadViewer(); + + this.addActiveScreenSubscription = this._stateService.fileData$ + .pipe( + filter(fileData => !!fileData), + switchMap(fileData => fileData.blob$), + // Skip document reload if file content hasn't changed + shareDistinctLast(), + tap(() => this._loadDocument()), + ) + .subscribe(); } ngOnChanges(changes: SimpleChanges): void { @@ -115,16 +137,12 @@ export class PdfViewerComponent implements OnInit, OnChanges { return; } - if (changes.fileData) { - this._loadDocument(); - } - if (changes.canPerformActions) { this._handleCustomActions(); } } - uploadFile(files: any) { + uploadFile(files: FileList) { const fileToCompare = files[0]; this.compareFileInput.nativeElement.value = null; @@ -140,7 +158,7 @@ export class PdfViewerComponent implements OnInit, OnChanges { await pdfNet.initialize(environment.licenseKey ? atob(environment.licenseKey) : null); const compareDocument = await pdfNet.PDFDoc.createFromBuffer(fileReader.result as ArrayBuffer); - const currentDocument = await pdfNet.PDFDoc.createFromBuffer(await this.fileData.arrayBuffer()); + const currentDocument = await pdfNet.PDFDoc.createFromBuffer(await this._stateService.fileData.blob$.value.arrayBuffer()); const loadCompareDocument = async () => { this._loadingService.start(); @@ -153,7 +171,7 @@ export class PdfViewerComponent implements OnInit, OnChanges { this.instance, this.file, () => { - this.viewModeService.set('COMPARE'); + this.viewModeService.compareMode = true; }, () => { this.utils.navigateToPage(1); @@ -190,10 +208,10 @@ export class PdfViewerComponent implements OnInit, OnChanges { } async closeCompareMode() { - this.viewModeService.set('STANDARD'); + this.viewModeService.compareMode = false; const pdfNet = this.instance.Core.PDFNet; await pdfNet.initialize(environment.licenseKey ? atob(environment.licenseKey) : null); - const currentDocument = await pdfNet.PDFDoc.createFromBuffer(await this.fileData.arrayBuffer()); + const currentDocument = await pdfNet.PDFDoc.createFromBuffer(await this._stateService.fileData.blob$.value.arrayBuffer()); this.instance.UI.loadDocument(currentDocument, { filename: this.file ? this.file.filename : 'document.pdf', }); @@ -202,7 +220,7 @@ export class PdfViewerComponent implements OnInit, OnChanges { this.utils.navigateToPage(1); } - async loadViewer() { + private async _loadViewer() { this.instance = await WebViewer( { licenseKey: environment.licenseKey ? atob(environment.licenseKey) : null, @@ -211,7 +229,7 @@ export class PdfViewerComponent implements OnInit, OnChanges { css: this._convertPath('/assets/pdftron/stylesheet.css'), backendType: 'ems', }, - this.viewer.nativeElement, + this.viewer.nativeElement as HTMLElement, ); this.documentViewer = this.instance.Core.documentViewer; @@ -223,7 +241,7 @@ export class PdfViewerComponent implements OnInit, OnChanges { this.utils.disableHotkeys(); this._configureTextPopup(); - this.annotationManager.addEventListener('annotationSelected', (annotations, action) => { + this.annotationManager.addEventListener('annotationSelected', (annotations: Annotation[], action) => { this.annotationSelected.emit(this.annotationManager.getSelectedAnnotations().map(ann => ann.Id)); if (action === 'deselected') { this._toggleRectangleAnnotationAction(true); @@ -233,7 +251,7 @@ export class PdfViewerComponent implements OnInit, OnChanges { } }); - this.annotationManager.addEventListener('annotationChanged', annotations => { + this.annotationManager.addEventListener('annotationChanged', (annotations: Annotation[]) => { // when a rectangle is drawn, // it returns one annotation with tool name 'AnnotationCreateRectangle; // this will auto select rectangle after drawing @@ -243,7 +261,7 @@ export class PdfViewerComponent implements OnInit, OnChanges { } }); - this.documentViewer.addEventListener('pageNumberUpdated', pageNumber => { + this.documentViewer.addEventListener('pageNumberUpdated', (pageNumber: number) => { if (this.shouldDeselectAnnotationsOnPageChange) { this.utils.deselectAllAnnotations(); } @@ -253,9 +271,9 @@ export class PdfViewerComponent implements OnInit, OnChanges { this.documentViewer.addEventListener('documentLoaded', this._setReadyAndInitialState); - this.documentViewer.addEventListener('keyUp', $event => { + this.documentViewer.addEventListener('keyUp', ($event: KeyboardEvent) => { // arrows and full-screen - if ($event.target?.tagName?.toLowerCase() !== 'input') { + if (($event.target as HTMLElement)?.tagName?.toLowerCase() !== 'input') { if ($event.key.startsWith('Arrow') || $event.key === 'f') { this._ngZone.run(() => { this.keyUp.emit($event); @@ -265,7 +283,7 @@ export class PdfViewerComponent implements OnInit, OnChanges { } } - if (ALLOWED_KEYBOARD_SHORTCUTS.indexOf($event.key) < 0) { + if (!ALLOWED_KEYBOARD_SHORTCUTS.includes($event.key)) { $event.preventDefault(); $event.stopPropagation(); } @@ -293,8 +311,6 @@ export class PdfViewerComponent implements OnInit, OnChanges { } } }); - - this._loadDocument(); } private _setInitialDisplayMode() { @@ -354,13 +370,14 @@ export class PdfViewerComponent implements OnInit, OnChanges { type: 'actionButton', element: 'tooltips', dataElement: dataElements.TOGGLE_TOOLTIPS, - img: this._convertPath('/assets/icons/general/pdftron-action-toggle-tooltips.svg'), + img: this._toggleTooltipsIcon, title: this._toggleTooltipsBtnTitle, onClick: async () => { await this._userPreferenceService.toggleFilePreviewTooltipsPreference(); this._updateTooltipsVisibility(); this.instance.UI.updateElement(dataElements.TOGGLE_TOOLTIPS, { title: this._toggleTooltipsBtnTitle, + img: this._toggleTooltipsIcon, }); }, }, @@ -381,36 +398,34 @@ export class PdfViewerComponent implements OnInit, OnChanges { const originalHeaderItems = header.getItems(); originalHeaderItems.splice(8, 0, ...headerItems); - if (this._userPreferenceService.areDevFeaturesEnabled) { - const devHeaderItems = [ - { - type: 'actionButton', - element: 'compare', - dataElement: dataElements.COMPARE_BUTTON, - img: this._convertPath('/assets/icons/general/pdftron-action-compare.svg'), - title: 'Compare', - onClick: () => { - this.compareFileInput.nativeElement.click(); - }, + const compareHeaderItems = [ + { + type: 'actionButton', + element: 'compare', + dataElement: dataElements.COMPARE_BUTTON, + img: this._convertPath('/assets/icons/general/pdftron-action-compare.svg'), + title: 'Compare', + onClick: () => { + this.compareFileInput.nativeElement.click(); }, - { - type: 'actionButton', - element: 'closeCompare', - dataElement: dataElements.CLOSE_COMPARE_BUTTON, - img: this._convertPath('/assets/icons/general/pdftron-action-close-compare.svg'), - title: 'Leave Compare Mode', - onClick: async () => { - await this.closeCompareMode(); - }, + }, + { + type: 'actionButton', + element: 'closeCompare', + dataElement: dataElements.CLOSE_COMPARE_BUTTON, + img: this._convertPath('/assets/icons/general/pdftron-action-close-compare.svg'), + title: 'Leave Compare Mode', + onClick: async () => { + await this.closeCompareMode(); }, - { - type: 'divider', - dataElement: dataElements.COMPARE_TOOL_DIVIDER, - }, - ]; + }, + { + type: 'divider', + dataElement: dataElements.COMPARE_TOOL_DIVIDER, + }, + ]; - originalHeaderItems.splice(9, 0, ...devHeaderItems); - } + originalHeaderItems.splice(9, 0, ...compareHeaderItems); }); this.instance.UI.disableElements([dataElements.CLOSE_COMPARE_BUTTON]); @@ -561,7 +576,7 @@ export class PdfViewerComponent implements OnInit, OnChanges { } private _addManualRedactionOfType(type: ManualRedactionEntryType) { - const selectedQuads = this.documentViewer.getSelectedTextQuads(); + const selectedQuads: Readonly> = this.documentViewer.getSelectedTextQuads(); const text = this.documentViewer.getSelectedText(); const manualRedaction = this._getManualRedaction(selectedQuads, text, true); this.manualAnnotationRequested.emit(new ManualRedactionEntryWrapper(selectedQuads, manualRedaction, type)); @@ -617,7 +632,7 @@ export class PdfViewerComponent implements OnInit, OnChanges { for (const quad of quads[key]) { const page = parseInt(key, 10); const pageHeight = this.documentViewer.getPageHeight(page); - entry.positions.push(toPosition(page, pageHeight, convertQuads ? this.utils.translateQuads(page, quad) : quad)); + entry.positions.push(toPosition(page, pageHeight, convertQuads ? this.utils.translateQuad(page, quad) : quad)); } } @@ -627,11 +642,7 @@ export class PdfViewerComponent implements OnInit, OnChanges { } private _loadDocument() { - if (!this.fileData) { - return; - } - - this.instance.UI.loadDocument(this.fileData, { + this.instance.UI.loadDocument(this._stateService.fileData.blob$.value, { filename: this.file ? this.file.filename : 'document.pdf', }); } @@ -640,7 +651,7 @@ export class PdfViewerComponent implements OnInit, OnChanges { this._ngZone.run(() => { this.utils.ready = true; this.viewerReady.emit(this.instance); - const routePageNumber = this._activatedRoute.snapshot.queryParams.page; + const routePageNumber: number = this._activatedRoute.snapshot.queryParams.page; this.pageChanged.emit(routePageNumber || 1); this._setInitialDisplayMode(); this._updateTooltipsVisibility(); diff --git a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/view-switch/view-switch.component.html b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/view-switch/view-switch.component.html index 4daf4a4ee..c99ce4ba5 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/view-switch/view-switch.component.html +++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/view-switch/view-switch.component.html @@ -1,5 +1,5 @@ -
{{ 'file-preview.standard' | translate }} -
+ -
{{ 'file-preview.delta' | translate }} -
+ -
{{ 'file-preview.redacted' | translate }} -
+
diff --git a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/view-switch/view-switch.component.ts b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/view-switch/view-switch.component.ts index 283aa7f62..b069922d9 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/view-switch/view-switch.component.ts +++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/components/view-switch/view-switch.component.ts @@ -1,10 +1,12 @@ import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core'; import { File, ViewMode } from '@red/domain'; import { ViewModeService } from '../../services/view-mode.service'; -import { FileDataModel } from '@models/file/file-data.model'; +import { FilePreviewStateService } from '../../services/file-preview-state.service'; +import { Observable } from 'rxjs'; +import { filter, switchMap } from 'rxjs/operators'; @Component({ - selector: 'redaction-view-switch [file] [fileData]', + selector: 'redaction-view-switch [file]', templateUrl: './view-switch.component.html', styleUrls: ['./view-switch.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, @@ -12,19 +14,18 @@ import { FileDataModel } from '@models/file/file-data.model'; export class ViewSwitchComponent implements OnChanges { @Output() readonly switchView = new EventEmitter(); @Input() file: File; - @Input() fileData: FileDataModel; - canSwitchToDeltaView = false; + readonly canSwitchToDeltaView$: Observable; canSwitchToRedactedView = false; - constructor(readonly viewModeService: ViewModeService) {} + constructor(readonly viewModeService: ViewModeService, private readonly _stateService: FilePreviewStateService) { + this.canSwitchToDeltaView$ = this._stateService.fileData$.pipe( + filter(fileData => !!fileData), + switchMap(fileData => fileData?.hasChangeLog$), + ); + } ngOnChanges(changes: SimpleChanges) { - if (changes.fileData) { - const fileData = changes.fileData.currentValue as FileDataModel; - this.canSwitchToDeltaView = fileData?.hasChangeLog; - } - if (changes.file) { const file = changes?.file.currentValue as File; this.canSwitchToRedactedView = !file.analysisRequired && !file.excluded; diff --git a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/file-preview-screen.component.html b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/file-preview-screen.component.html index 33575b2ea..b90dd8d93 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/file-preview-screen.component.html +++ b/apps/red-ui/src/app/modules/dossier/screens/file-preview-screen/file-preview-screen.component.html @@ -1,9 +1,9 @@ - +