Merge branch 'master' into RED-2769
This commit is contained in:
commit
a1726be3f9
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"cli": {
|
"cli": {
|
||||||
"analytics": "4b8eed12-a1e6-4b7a-9ea2-925b27941271"
|
"analytics": false
|
||||||
},
|
},
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"projects": {
|
"projects": {
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
<router-outlet></router-outlet>
|
<router-outlet></router-outlet>
|
||||||
<iqser-full-page-loading-indicator></iqser-full-page-loading-indicator>
|
<iqser-full-page-loading-indicator></iqser-full-page-loading-indicator>
|
||||||
|
<iqser-connection-status></iqser-connection-status>
|
||||||
<iqser-full-page-error></iqser-full-page-error>
|
<iqser-full-page-error></iqser-full-page-error>
|
||||||
|
|||||||
@ -1,30 +1,29 @@
|
|||||||
<form (submit)="executeCurrentAction()">
|
<iqser-input-with-action
|
||||||
<iqser-input-with-action
|
(action)="executeCurrentAction()"
|
||||||
(click)="openMenuIfValue()"
|
(click)="openMenuIfValue()"
|
||||||
(valueChange)="valueChanges$.next($event)"
|
(valueChange)="valueChanges$.next($event)"
|
||||||
[placeholder]="placeholder"
|
[placeholder]="placeholder"
|
||||||
></iqser-input-with-action>
|
></iqser-input-with-action>
|
||||||
|
|
||||||
<mat-menu #menu="matMenu" class="search-menu" xPosition="after">
|
<mat-menu #menu="matMenu" class="search-menu" xPosition="after">
|
||||||
<ng-template matMenuContent>
|
<ng-template matMenuContent>
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<button
|
<button
|
||||||
(click)="item.action(valueChanges$.getValue())"
|
(click)="item.action(valueChanges$.getValue())"
|
||||||
*ngFor="let item of shownActions; let index = index"
|
*ngFor="let item of shownActions; let index = index"
|
||||||
[class.highlight]="(currentActionIdx$ | async) === index"
|
[class.highlight]="(currentActionIdx$ | async) === index"
|
||||||
class="spotlight-row pointer"
|
class="spotlight-row pointer"
|
||||||
>
|
>
|
||||||
<mat-icon [svgIcon]="item.icon"></mat-icon>
|
<mat-icon [svgIcon]="item.icon"></mat-icon>
|
||||||
<span>{{ item.text }}</span>
|
<span>{{ item.text }}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</mat-menu>
|
</mat-menu>
|
||||||
|
|
||||||
<!-- https://material.angular.io/components/menu/overview#toggling-the-menu-programmatically -->
|
<!-- https://material.angular.io/components/menu/overview#toggling-the-menu-programmatically -->
|
||||||
<!-- To toggle menu programmatically a matMenuTriggerFor directive is needed -->
|
<!-- To toggle menu programmatically a matMenuTriggerFor directive is needed -->
|
||||||
<div [matMenuTriggerFor]="menu"></div>
|
<div [matMenuTriggerFor]="menu"></div>
|
||||||
|
|
||||||
<!-- A hack to avoid subscribing in component -->
|
<!-- A hack to avoid subscribing in component -->
|
||||||
<ng-container *ngIf="showActions$ | async"></ng-container>
|
<ng-container *ngIf="showActions$ | async"></ng-container>
|
||||||
</form>
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { AnnotationWrapper } from './annotation.wrapper';
|
import { AnnotationWrapper } from './annotation.wrapper';
|
||||||
import { isArray } from 'rxjs/internal-compatibility';
|
|
||||||
import { User } from '@red/domain';
|
import { User } from '@red/domain';
|
||||||
|
import { isArray } from 'lodash';
|
||||||
|
|
||||||
export class AnnotationPermissions {
|
export class AnnotationPermissions {
|
||||||
canUndo = true;
|
canUndo = true;
|
||||||
|
|||||||
@ -283,11 +283,7 @@ export class AnnotationWrapper {
|
|||||||
|
|
||||||
private static _setSuperType(annotationWrapper: AnnotationWrapper, redactionLogEntryWrapper: RedactionLogEntryWrapper) {
|
private static _setSuperType(annotationWrapper: AnnotationWrapper, redactionLogEntryWrapper: RedactionLogEntryWrapper) {
|
||||||
if (redactionLogEntryWrapper.recommendation) {
|
if (redactionLogEntryWrapper.recommendation) {
|
||||||
if (redactionLogEntryWrapper.redacted) {
|
annotationWrapper.superType = 'recommendation';
|
||||||
annotationWrapper.superType = 'recommendation';
|
|
||||||
} else {
|
|
||||||
annotationWrapper.superType = 'skipped';
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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 { AnnotationWrapper } from './annotation.wrapper';
|
||||||
import { RedactionLogEntryWrapper } from './redaction-log-entry.wrapper';
|
import { RedactionLogEntryWrapper } from './redaction-log-entry.wrapper';
|
||||||
import * as moment from 'moment';
|
import * as moment from 'moment';
|
||||||
|
import { BehaviorSubject } from 'rxjs';
|
||||||
|
|
||||||
export class FileDataModel {
|
export class FileDataModel {
|
||||||
static readonly DELTA_VIEW_TIME = 10 * 60 * 1000; // 10 minutes;
|
static readonly DELTA_VIEW_TIME = 10 * 60 * 1000; // 10 minutes;
|
||||||
|
|
||||||
hasChangeLog: boolean;
|
|
||||||
allAnnotations: AnnotationWrapper[];
|
allAnnotations: AnnotationWrapper[];
|
||||||
|
readonly hasChangeLog$ = new BehaviorSubject<boolean>(false);
|
||||||
|
readonly blob$ = new BehaviorSubject<Blob>(undefined);
|
||||||
|
readonly file$ = new BehaviorSubject<File>(undefined);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public file: File,
|
private readonly _file: File,
|
||||||
public fileData: Blob,
|
private readonly _blob: Blob,
|
||||||
private _redactionLog: IRedactionLog,
|
private _redactionLog: IRedactionLog,
|
||||||
public viewedPages?: IViewedPage[],
|
public viewedPages?: IViewedPage[],
|
||||||
private _dictionaryData?: { [p: string]: Dictionary },
|
private _dictionaryData?: { [p: string]: Dictionary },
|
||||||
private _areDevFeaturesEnabled?: boolean,
|
private _areDevFeaturesEnabled?: boolean,
|
||||||
) {
|
) {
|
||||||
|
this.file$.next(_file);
|
||||||
|
this.blob$.next(_blob);
|
||||||
this._buildAllAnnotations();
|
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) {
|
set redactionLog(redactionLog: IRedactionLog) {
|
||||||
this._redactionLog = redactionLog;
|
this._redactionLog = redactionLog;
|
||||||
this._buildAllAnnotations();
|
this._buildAllAnnotations();
|
||||||
}
|
}
|
||||||
|
|
||||||
get redactionLog() {
|
|
||||||
return this._redactionLog;
|
|
||||||
}
|
|
||||||
|
|
||||||
getVisibleAnnotations(viewMode: ViewMode) {
|
getVisibleAnnotations(viewMode: ViewMode) {
|
||||||
return this.allAnnotations.filter(annotation => {
|
return this.allAnnotations.filter(annotation => {
|
||||||
if (viewMode === 'STANDARD') {
|
if (viewMode === 'STANDARD') {
|
||||||
@ -47,7 +59,7 @@ export class FileDataModel {
|
|||||||
const previousAnnotations = this.allAnnotations || [];
|
const previousAnnotations = this.allAnnotations || [];
|
||||||
this.allAnnotations = entries
|
this.allAnnotations = entries
|
||||||
.map(entry => AnnotationWrapper.fromData(entry))
|
.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) {
|
if (!this._areDevFeaturesEnabled) {
|
||||||
this.allAnnotations = this.allAnnotations.filter(annotation => !annotation.isFalsePositive);
|
this.allAnnotations = this.allAnnotations.filter(annotation => !annotation.isFalsePositive);
|
||||||
@ -121,11 +133,11 @@ export class FileDataModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _isChangeLogEntry(redactionLogEntry: IRedactionLogEntry, wrapper: RedactionLogEntryWrapper) {
|
private _isChangeLogEntry(redactionLogEntry: IRedactionLogEntry, wrapper: RedactionLogEntryWrapper) {
|
||||||
if (this.file.numberOfAnalyses > 1) {
|
if (this._file.numberOfAnalyses > 1) {
|
||||||
redactionLogEntry.changes.sort((a, b) => moment(a.dateTime).valueOf() - moment(b.dateTime).valueOf());
|
const viableChanges = redactionLogEntry.changes.filter(c => c.analysisNumber > 1);
|
||||||
|
viableChanges.sort((a, b) => moment(a.dateTime).valueOf() - moment(b.dateTime).valueOf());
|
||||||
|
|
||||||
const lastChange =
|
const lastChange = viableChanges.length >= 1 ? viableChanges[viableChanges.length - 1] : undefined;
|
||||||
redactionLogEntry.changes.length >= 1 ? redactionLogEntry.changes[redactionLogEntry.changes.length - 1] : undefined;
|
|
||||||
const page = redactionLogEntry.positions?.[0].page;
|
const page = redactionLogEntry.positions?.[0].page;
|
||||||
|
|
||||||
const viewedPage = this.viewedPages.filter(p => p.page === page).pop();
|
const viewedPage = this.viewedPages.filter(p => p.page === page).pop();
|
||||||
@ -134,14 +146,14 @@ export class FileDataModel {
|
|||||||
if (viewedPage) {
|
if (viewedPage) {
|
||||||
const viewTime = moment(viewedPage.viewedTime).valueOf() - FileDataModel.DELTA_VIEW_TIME;
|
const viewTime = moment(viewedPage.viewedTime).valueOf() - FileDataModel.DELTA_VIEW_TIME;
|
||||||
// these are all unseen changes
|
// 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
|
// at least one unseen change
|
||||||
if (relevantChanges.length > 0) {
|
if (relevantChanges.length > 0) {
|
||||||
// at least 1 relevant change
|
// at least 1 relevant change
|
||||||
wrapper.changeLogType = relevantChanges[relevantChanges.length - 1].type;
|
wrapper.changeLogType = relevantChanges[relevantChanges.length - 1].type;
|
||||||
wrapper.isChangeLogEntry = true;
|
wrapper.isChangeLogEntry = true;
|
||||||
viewedPage.showAsUnseen = moment(viewedPage.viewedTime).valueOf() < moment(lastChange.dateTime).valueOf();
|
viewedPage.showAsUnseen = moment(viewedPage.viewedTime).valueOf() < moment(lastChange.dateTime).valueOf();
|
||||||
this.hasChangeLog = true;
|
this.hasChangeLog$.next(true);
|
||||||
} else {
|
} else {
|
||||||
// no relevant changes - hide removed anyway
|
// no relevant changes - hide removed anyway
|
||||||
wrapper.isChangeLogEntry = false;
|
wrapper.isChangeLogEntry = false;
|
||||||
|
|||||||
@ -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;
|
|
||||||
@ -7,25 +7,25 @@
|
|||||||
}}</mat-slide-toggle>
|
}}</mat-slide-toggle>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="options-content" *ngIf="isCategoryActive(category)">
|
<div *ngIf="isCategoryActive(category)" class="options-content">
|
||||||
<div class="radio-container" *ngIf="category === 'emailNotifications'">
|
<!-- <div class="radio-container" *ngIf="category === 'emailNotifications'">-->
|
||||||
<div class="radio-button" *ngFor="let type of emailNotificationScheduleTypes">
|
<!-- <div class="radio-button" *ngFor="let type of emailNotificationScheduleTypes">-->
|
||||||
<iqser-round-checkbox [active]="getEmailNotificationType() === type" (click)="setEmailNotificationType(type)">
|
<!-- <iqser-round-checkbox [active]="getEmailNotificationType() === type" (click)="setEmailNotificationType(type)">-->
|
||||||
</iqser-round-checkbox>
|
<!-- </iqser-round-checkbox>-->
|
||||||
<span> {{ translations[type.toLocaleLowerCase()] | translate }} </span>
|
<!-- <span> {{ translations[type.toLocaleLowerCase()] | translate }} </span>-->
|
||||||
</div>
|
<!-- </div>-->
|
||||||
</div>
|
<!-- </div>-->
|
||||||
|
|
||||||
<div class="statement" translate="notifications-screen.options-title"></div>
|
<div class="statement" translate="notifications-screen.options-title"></div>
|
||||||
|
|
||||||
<div class="group" *ngFor="let key of notificationGroupsKeys; let i = index">
|
<div *ngFor="let key of notificationGroupsKeys; let i = index" class="group">
|
||||||
<div class="group-title" [translate]="translations[key]"></div>
|
<div [translate]="translations[key]" class="group-title"></div>
|
||||||
<div class="iqser-input-group">
|
<div class="iqser-input-group">
|
||||||
<mat-checkbox
|
<mat-checkbox
|
||||||
*ngFor="let preference of notificationGroupsValues[i]"
|
|
||||||
color="primary"
|
|
||||||
[checked]="isPreferenceChecked(category, preference)"
|
|
||||||
(change)="addRemovePreference($event.checked, category, preference)"
|
(change)="addRemovePreference($event.checked, category, preference)"
|
||||||
|
*ngFor="let preference of notificationGroupsValues[i]"
|
||||||
|
[checked]="isPreferenceChecked(category, preference)"
|
||||||
|
color="primary"
|
||||||
>
|
>
|
||||||
{{ translations[preference] | translate }}
|
{{ translations[preference] | translate }}
|
||||||
</mat-checkbox>
|
</mat-checkbox>
|
||||||
|
|||||||
@ -4,8 +4,12 @@ import { notificationsTranslations } from '../../../translations/notifications-t
|
|||||||
import { NotificationPreferencesService } from '../../../services/notification-preferences.service';
|
import { NotificationPreferencesService } from '../../../services/notification-preferences.service';
|
||||||
import { LoadingService, Toaster } from '@iqser/common-ui';
|
import { LoadingService, Toaster } from '@iqser/common-ui';
|
||||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||||
import { NotificationCategoriesValues, NotificationGroupsKeys, NotificationGroupsValues } from '../constants';
|
import {
|
||||||
import { EmailNotificationScheduleTypesValues } from '@red/domain';
|
EmailNotificationScheduleTypesValues,
|
||||||
|
NotificationCategoriesValues,
|
||||||
|
NotificationGroupsKeys,
|
||||||
|
NotificationGroupsValues,
|
||||||
|
} from '@red/domain';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'redaction-notifications-screen',
|
selector: 'redaction-notifications-screen',
|
||||||
@ -33,16 +37,6 @@ export class NotificationsScreenComponent implements OnInit {
|
|||||||
await this._initializeForm();
|
await this._initializeForm();
|
||||||
}
|
}
|
||||||
|
|
||||||
private _getForm(): FormGroup {
|
|
||||||
return this._formBuilder.group({
|
|
||||||
inAppNotificationsEnabled: [undefined],
|
|
||||||
emailNotificationsEnabled: [undefined],
|
|
||||||
emailNotificationType: [undefined],
|
|
||||||
emailNotifications: [undefined],
|
|
||||||
inAppNotifications: [undefined],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
isCategoryActive(category: string) {
|
isCategoryActive(category: string) {
|
||||||
return this.formGroup.get(`${category}Enabled`).value;
|
return this.formGroup.get(`${category}Enabled`).value;
|
||||||
}
|
}
|
||||||
@ -80,6 +74,16 @@ export class NotificationsScreenComponent implements OnInit {
|
|||||||
this._loadingService.stop();
|
this._loadingService.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _getForm(): FormGroup {
|
||||||
|
return this._formBuilder.group({
|
||||||
|
inAppNotificationsEnabled: [undefined],
|
||||||
|
emailNotificationsEnabled: [undefined],
|
||||||
|
emailNotificationType: [undefined],
|
||||||
|
emailNotifications: [undefined],
|
||||||
|
inAppNotifications: [undefined],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private async _initializeForm() {
|
private async _initializeForm() {
|
||||||
this._loadingService.start();
|
this._loadingService.start();
|
||||||
|
|
||||||
|
|||||||
@ -73,7 +73,7 @@ export class UserProfileScreenComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.profileChanged) {
|
if (this.profileChanged) {
|
||||||
const value = this.form.value as IProfile;
|
const value = this.form.getRawValue() as IProfile;
|
||||||
delete value.language;
|
delete value.language;
|
||||||
|
|
||||||
await this._userService
|
await this._userService
|
||||||
|
|||||||
@ -12,7 +12,18 @@ import {
|
|||||||
|
|
||||||
import { curveLinear } from 'd3-shape';
|
import { curveLinear } from 'd3-shape';
|
||||||
import { scaleBand, scaleLinear, scalePoint, scaleTime } from 'd3-scale';
|
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({
|
@Component({
|
||||||
// eslint-disable-next-line @angular-eslint/component-selector
|
// eslint-disable-next-line @angular-eslint/component-selector
|
||||||
@ -25,7 +36,7 @@ export class ComboChartComponent extends BaseChartComponent {
|
|||||||
@Input() curve: any = curveLinear;
|
@Input() curve: any = curveLinear;
|
||||||
@Input() legend = false;
|
@Input() legend = false;
|
||||||
@Input() legendTitle = 'Legend';
|
@Input() legendTitle = 'Legend';
|
||||||
@Input() legendPosition = 'right';
|
@Input() legendPosition: LegendPosition = LegendPosition.Right;
|
||||||
@Input() xAxis;
|
@Input() xAxis;
|
||||||
@Input() yAxis;
|
@Input() yAxis;
|
||||||
@Input() showXAxisLabel;
|
@Input() showXAxisLabel;
|
||||||
@ -38,33 +49,33 @@ export class ComboChartComponent extends BaseChartComponent {
|
|||||||
@Input() gradient: boolean;
|
@Input() gradient: boolean;
|
||||||
@Input() showGridLines = true;
|
@Input() showGridLines = true;
|
||||||
@Input() activeEntries: any[] = [];
|
@Input() activeEntries: any[] = [];
|
||||||
@Input() schemeType: string;
|
@Input() schemeType: ScaleType;
|
||||||
@Input() xAxisTickFormatting: any;
|
@Input() xAxisTickFormatting: any;
|
||||||
@Input() yAxisTickFormatting: any;
|
@Input() yAxisTickFormatting: any;
|
||||||
@Input() yRightAxisTickFormatting: any;
|
@Input() yRightAxisTickFormatting: any;
|
||||||
@Input() roundDomains = false;
|
@Input() roundDomains = false;
|
||||||
@Input() colorSchemeLine: any;
|
@Input() colorSchemeLine: Color;
|
||||||
@Input() autoScale;
|
@Input() autoScale;
|
||||||
@Input() lineChart: any;
|
@Input() lineChart: ILineChartSeries[];
|
||||||
@Input() yLeftAxisScaleFactor: any;
|
@Input() yLeftAxisScaleFactor: any;
|
||||||
@Input() yRightAxisScaleFactor: any;
|
@Input() yRightAxisScaleFactor: any;
|
||||||
@Input() rangeFillOpacity: number;
|
@Input() rangeFillOpacity: number;
|
||||||
@Input() animations = true;
|
@Input() animations = true;
|
||||||
@Input() noBarWhenZero = true;
|
@Input() noBarWhenZero = true;
|
||||||
|
|
||||||
@Output() activate: EventEmitter<any> = new EventEmitter();
|
@Output() activate = new EventEmitter<{ value; entries: unknown[] }>();
|
||||||
@Output() deactivate: EventEmitter<any> = new EventEmitter();
|
@Output() deactivate = new EventEmitter<{ value; entries: unknown[] }>();
|
||||||
|
|
||||||
@ContentChild('tooltipTemplate') tooltipTemplate: TemplateRef<any>;
|
@ContentChild('tooltipTemplate') tooltipTemplate: TemplateRef<unknown>;
|
||||||
@ContentChild('seriesTooltipTemplate') seriesTooltipTemplate: TemplateRef<any>;
|
@ContentChild('seriesTooltipTemplate') seriesTooltipTemplate: TemplateRef<unknown>;
|
||||||
|
|
||||||
@ViewChild(LineSeriesComponent) lineSeriesComponent: LineSeriesComponent;
|
@ViewChild(LineSeriesComponent) lineSeriesComponent: LineSeriesComponent;
|
||||||
|
|
||||||
dims: ViewDimensions;
|
dims: ViewDimensions;
|
||||||
xScale: any;
|
xScale: any;
|
||||||
yScale: any;
|
yScale: any;
|
||||||
xDomain: any;
|
xDomain: string[] | number[];
|
||||||
yDomain: any;
|
yDomain: string[] | number[];
|
||||||
transform: string;
|
transform: string;
|
||||||
colors: ColorHelper;
|
colors: ColorHelper;
|
||||||
colorsLine: ColorHelper;
|
colorsLine: ColorHelper;
|
||||||
@ -72,19 +83,19 @@ export class ComboChartComponent extends BaseChartComponent {
|
|||||||
xAxisHeight = 0;
|
xAxisHeight = 0;
|
||||||
yAxisWidth = 0;
|
yAxisWidth = 0;
|
||||||
legendOptions: any;
|
legendOptions: any;
|
||||||
scaleType = 'linear';
|
scaleType: ScaleType = ScaleType.Linear;
|
||||||
xScaleLine;
|
xScaleLine;
|
||||||
yScaleLine;
|
yScaleLine;
|
||||||
xDomainLine;
|
xDomainLine;
|
||||||
yDomainLine;
|
yDomainLine;
|
||||||
seriesDomain;
|
seriesDomain;
|
||||||
scaledAxis;
|
scaledAxis;
|
||||||
combinedSeries;
|
combinedSeries: ILineChartSeries[];
|
||||||
xSet;
|
xSet;
|
||||||
filteredDomain;
|
filteredDomain;
|
||||||
hoveredVertical;
|
hoveredVertical;
|
||||||
yOrientLeft = 'left';
|
yOrientLeft: Orientation = Orientation.Left;
|
||||||
yOrientRight = 'right';
|
yOrientRight: Orientation = Orientation.Right;
|
||||||
legendSpacing = 0;
|
legendSpacing = 0;
|
||||||
bandwidth: number;
|
bandwidth: number;
|
||||||
barPadding = 8;
|
barPadding = 8;
|
||||||
@ -176,15 +187,11 @@ export class ComboChartComponent extends BaseChartComponent {
|
|||||||
return this.combinedSeries.map(d => d.name);
|
return this.combinedSeries.map(d => d.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
isDate(value): boolean {
|
isDate(value): value is Date {
|
||||||
if (value instanceof Date) {
|
return value instanceof Date;
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getScaleType(values): string {
|
getScaleType(values): ScaleType {
|
||||||
let date = true;
|
let date = true;
|
||||||
let num = true;
|
let num = true;
|
||||||
|
|
||||||
@ -199,16 +206,16 @@ export class ComboChartComponent extends BaseChartComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (date) {
|
if (date) {
|
||||||
return 'time';
|
return ScaleType.Time;
|
||||||
}
|
}
|
||||||
if (num) {
|
if (num) {
|
||||||
return 'linear';
|
return ScaleType.Linear;
|
||||||
}
|
}
|
||||||
return 'ordinal';
|
return ScaleType.Ordinal;
|
||||||
}
|
}
|
||||||
|
|
||||||
getXDomainLine(): any[] {
|
getXDomainLine(): any[] {
|
||||||
let values = [];
|
let values: number[] = [];
|
||||||
|
|
||||||
for (const results of this.lineChart) {
|
for (const results of this.lineChart) {
|
||||||
for (const d of results.series) {
|
for (const d of results.series) {
|
||||||
@ -239,7 +246,7 @@ export class ComboChartComponent extends BaseChartComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getYDomainLine(): any[] {
|
getYDomainLine(): any[] {
|
||||||
const domain = [];
|
const domain: number[] = [];
|
||||||
|
|
||||||
for (const results of this.lineChart) {
|
for (const results of this.lineChart) {
|
||||||
for (const d of results.series) {
|
for (const d of results.series) {
|
||||||
@ -263,7 +270,7 @@ export class ComboChartComponent extends BaseChartComponent {
|
|||||||
const max = Math.max(...domain);
|
const max = Math.max(...domain);
|
||||||
if (this.yRightAxisScaleFactor) {
|
if (this.yRightAxisScaleFactor) {
|
||||||
const minMax = this.yRightAxisScaleFactor(min, max);
|
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 {
|
} else {
|
||||||
min = Math.min(0, min);
|
min = Math.min(0, min);
|
||||||
return [min, max];
|
return [min, max];
|
||||||
@ -317,12 +324,12 @@ export class ComboChartComponent extends BaseChartComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getYDomain() {
|
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 min = Math.min(0, ...values);
|
||||||
const max = Math.max(...values);
|
const max = Math.max(...values);
|
||||||
if (this.yLeftAxisScaleFactor) {
|
if (this.yLeftAxisScaleFactor) {
|
||||||
const minMax = this.yLeftAxisScaleFactor(min, max);
|
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 {
|
} else {
|
||||||
return [min, max];
|
return [min, max];
|
||||||
}
|
}
|
||||||
@ -333,7 +340,7 @@ export class ComboChartComponent extends BaseChartComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setColors(): void {
|
setColors(): void {
|
||||||
let domain;
|
let domain: number[] | string[];
|
||||||
if (this.schemeType === 'ordinal') {
|
if (this.schemeType === 'ordinal') {
|
||||||
domain = this.xDomain;
|
domain = this.xDomain;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, Output } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, Output } from '@angular/core';
|
||||||
import { animate, style, transition, trigger } from '@angular/animations';
|
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({
|
@Component({
|
||||||
// eslint-disable-next-line @angular-eslint/component-selector
|
// eslint-disable-next-line @angular-eslint/component-selector
|
||||||
@ -17,7 +17,7 @@ import { formatLabel } from '@swimlane/ngx-charts';
|
|||||||
[fill]="bar.color"
|
[fill]="bar.color"
|
||||||
[stops]="bar.gradientStops"
|
[stops]="bar.gradientStops"
|
||||||
[data]="bar.data"
|
[data]="bar.data"
|
||||||
[orientation]="'vertical'"
|
[orientation]="orientations.Vertical"
|
||||||
[roundEdges]="bar.roundEdges"
|
[roundEdges]="bar.roundEdges"
|
||||||
[gradient]="gradient"
|
[gradient]="gradient"
|
||||||
[isActive]="isActive(bar.data)"
|
[isActive]="isActive(bar.data)"
|
||||||
@ -27,8 +27,8 @@ import { formatLabel } from '@swimlane/ngx-charts';
|
|||||||
(deactivate)="deactivate.emit($event)"
|
(deactivate)="deactivate.emit($event)"
|
||||||
ngx-tooltip
|
ngx-tooltip
|
||||||
[tooltipDisabled]="tooltipDisabled"
|
[tooltipDisabled]="tooltipDisabled"
|
||||||
[tooltipPlacement]="0"
|
[tooltipPlacement]="tooltipPlacements.Top"
|
||||||
[tooltipType]="1"
|
[tooltipType]="tooltipTypes.tooltip"
|
||||||
[tooltipTitle]="bar.tooltipText"
|
[tooltipTitle]="bar.tooltipText"
|
||||||
></svg:g>
|
></svg:g>
|
||||||
`,
|
`,
|
||||||
@ -67,6 +67,9 @@ export class ComboSeriesVerticalComponent implements OnChanges {
|
|||||||
bars: any;
|
bars: any;
|
||||||
x: any;
|
x: any;
|
||||||
y: any;
|
y: any;
|
||||||
|
readonly tooltipTypes = StyleTypes;
|
||||||
|
readonly tooltipPlacements = PlacementTypes;
|
||||||
|
readonly orientations = BarOrientation;
|
||||||
|
|
||||||
ngOnChanges(): void {
|
ngOnChanges(): void {
|
||||||
this.update();
|
this.update();
|
||||||
@ -91,7 +94,7 @@ export class ComboSeriesVerticalComponent implements OnChanges {
|
|||||||
const formattedLabel = formatLabel(label);
|
const formattedLabel = formatLabel(label);
|
||||||
const roundEdges = this.type === 'standard';
|
const roundEdges = this.type === 'standard';
|
||||||
|
|
||||||
const bar: any = {
|
const bar: Bar = {
|
||||||
value,
|
value,
|
||||||
label,
|
label,
|
||||||
roundEdges,
|
roundEdges,
|
||||||
@ -101,8 +104,15 @@ export class ComboSeriesVerticalComponent implements OnChanges {
|
|||||||
height: 0,
|
height: 0,
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
|
ariaLabel: label,
|
||||||
|
tooltipText: label,
|
||||||
|
color: undefined,
|
||||||
|
gradientStops: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let offset0 = d0;
|
||||||
|
let offset1 = offset0 + value;
|
||||||
|
|
||||||
if (this.type === 'standard') {
|
if (this.type === 'standard') {
|
||||||
bar.height = Math.abs(this.yScale(value) - this.yScale(0));
|
bar.height = Math.abs(this.yScale(value) - this.yScale(0));
|
||||||
bar.x = this.xScale(label);
|
bar.x = this.xScale(label);
|
||||||
@ -113,18 +123,14 @@ export class ComboSeriesVerticalComponent implements OnChanges {
|
|||||||
bar.y = this.yScale(value);
|
bar.y = this.yScale(value);
|
||||||
}
|
}
|
||||||
} else if (this.type === 'stacked') {
|
} else if (this.type === 'stacked') {
|
||||||
const offset0 = d0;
|
|
||||||
const offset1 = offset0 + value;
|
|
||||||
d0 += value;
|
d0 += value;
|
||||||
|
|
||||||
bar.height = this.yScale(offset0) - this.yScale(offset1);
|
bar.height = this.yScale(offset0) - this.yScale(offset1);
|
||||||
bar.x = 0;
|
bar.x = 0;
|
||||||
bar.y = this.yScale(offset1);
|
bar.y = this.yScale(offset1);
|
||||||
bar.offset0 = offset0;
|
// bar.offset0 = offset0;
|
||||||
bar.offset1 = offset1;
|
// bar.offset1 = offset1;
|
||||||
} else if (this.type === 'normalized') {
|
} else if (this.type === 'normalized') {
|
||||||
let offset0 = d0;
|
|
||||||
let offset1 = offset0 + value;
|
|
||||||
d0 += value;
|
d0 += value;
|
||||||
|
|
||||||
if (total > 0) {
|
if (total > 0) {
|
||||||
@ -138,8 +144,8 @@ export class ComboSeriesVerticalComponent implements OnChanges {
|
|||||||
bar.height = this.yScale(offset0) - this.yScale(offset1);
|
bar.height = this.yScale(offset0) - this.yScale(offset1);
|
||||||
bar.x = 0;
|
bar.x = 0;
|
||||||
bar.y = this.yScale(offset1);
|
bar.y = this.yScale(offset1);
|
||||||
bar.offset0 = offset0;
|
// bar.offset0 = offset0;
|
||||||
bar.offset1 = offset1;
|
// bar.offset1 = offset1;
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
value = (offset1 - offset0).toFixed(2) + '%';
|
value = (offset1 - offset0).toFixed(2) + '%';
|
||||||
@ -152,8 +158,8 @@ export class ComboSeriesVerticalComponent implements OnChanges {
|
|||||||
bar.color = this.colors.getColor(value);
|
bar.color = this.colors.getColor(value);
|
||||||
bar.gradientStops = this.colors.getLinearGradientStops(value);
|
bar.gradientStops = this.colors.getLinearGradientStops(value);
|
||||||
} else {
|
} else {
|
||||||
bar.color = this.colors.getColor(bar.offset1);
|
bar.color = this.colors.getColor(offset1);
|
||||||
bar.gradientStops = this.colors.getLinearGradientStops(bar.offset1, bar.offset0);
|
bar.gradientStops = this.colors.getLinearGradientStops(offset1, offset0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,11 @@
|
|||||||
|
export interface ISeries {
|
||||||
|
name: number;
|
||||||
|
value: number;
|
||||||
|
min: number;
|
||||||
|
max: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ILineChartSeries {
|
||||||
|
name: string;
|
||||||
|
series: ISeries[];
|
||||||
|
}
|
||||||
@ -95,11 +95,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="dialog-actions">
|
<div class="dialog-actions">
|
||||||
<button (click)="save()" [disabled]="!valid || !changed" color="primary" mat-flat-button type="button">
|
<button (click)="save()" [disabled]="disabled" color="primary" mat-flat-button type="button">
|
||||||
{{ 'add-edit-dictionary.save' | translate }}
|
{{ 'add-edit-dictionary.save' | translate }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<iqser-circle-button class="dialog-close" icon="iqser:close" mat-dialog-close></iqser-circle-button>
|
<iqser-circle-button (action)="close()" class="dialog-close" icon="iqser:close"></iqser-circle-button>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@ -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 { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
@ -6,7 +6,7 @@ import { BaseDialogComponent, shareDistinctLast, Toaster } from '@iqser/common-u
|
|||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||||
import { AppStateService } from '@state/app-state.service';
|
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 { DictionaryService } from '@shared/services/dictionary.service';
|
||||||
import { Dictionary, IDictionary } from '@red/domain';
|
import { Dictionary, IDictionary } from '@red/domain';
|
||||||
import { UserService } from '@services/user.service';
|
import { UserService } from '@services/user.service';
|
||||||
@ -21,7 +21,6 @@ import { HttpStatusCode } from '@angular/common/http';
|
|||||||
})
|
})
|
||||||
export class AddEditDictionaryDialogComponent extends BaseDialogComponent {
|
export class AddEditDictionaryDialogComponent extends BaseDialogComponent {
|
||||||
readonly dictionary = this._data.dictionary;
|
readonly dictionary = this._data.dictionary;
|
||||||
readonly form: FormGroup = this._getForm(this.dictionary);
|
|
||||||
readonly canEditLabel$ = this._canEditLabel$;
|
readonly canEditLabel$ = this._canEditLabel$;
|
||||||
readonly technicalName$: Observable<string>;
|
readonly technicalName$: Observable<string>;
|
||||||
readonly dialogHeader = this._translateService.instant('add-edit-dictionary.title', {
|
readonly dialogHeader = this._translateService.instant('add-edit-dictionary.title', {
|
||||||
@ -29,7 +28,6 @@ export class AddEditDictionaryDialogComponent extends BaseDialogComponent {
|
|||||||
name: this._data.dictionary?.label,
|
name: this._data.dictionary?.label,
|
||||||
});
|
});
|
||||||
readonly hasColor$: Observable<boolean>;
|
readonly hasColor$: Observable<boolean>;
|
||||||
readonly disabled = false;
|
|
||||||
private readonly _dossierTemplateId = this._data.dossierTemplateId;
|
private readonly _dossierTemplateId = this._data.dossierTemplateId;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@ -39,37 +37,18 @@ export class AddEditDictionaryDialogComponent extends BaseDialogComponent {
|
|||||||
private readonly _appStateService: AppStateService,
|
private readonly _appStateService: AppStateService,
|
||||||
private readonly _translateService: TranslateService,
|
private readonly _translateService: TranslateService,
|
||||||
private readonly _dictionaryService: DictionaryService,
|
private readonly _dictionaryService: DictionaryService,
|
||||||
private readonly _dialogRef: MatDialogRef<AddEditDictionaryDialogComponent>,
|
protected readonly _injector: Injector,
|
||||||
|
protected readonly _dialogRef: MatDialogRef<AddEditDictionaryDialogComponent>,
|
||||||
@Inject(MAT_DIALOG_DATA)
|
@Inject(MAT_DIALOG_DATA)
|
||||||
private readonly _data: { readonly dictionary: Dictionary; readonly dossierTemplateId: string },
|
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.hasColor$ = this._colorEmpty$;
|
||||||
this.technicalName$ = this.form.get('label').valueChanges.pipe(map(value => this._toTechnicalName(value)));
|
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$() {
|
private get _canEditLabel$() {
|
||||||
return this.userService.currentUser$.pipe(
|
return this.userService.currentUser$.pipe(
|
||||||
map(user => user.isAdmin || !this._data.dictionary),
|
map(user => user.isAdmin || !this._data.dictionary),
|
||||||
@ -126,11 +105,11 @@ export class AddEditDictionaryDialogComponent extends BaseDialogComponent {
|
|||||||
|
|
||||||
private _toTechnicalName(value: string) {
|
private _toTechnicalName(value: string) {
|
||||||
const existingTechnicalNames = Object.keys(this._appStateService.dictionaryData[this._dossierTemplateId]);
|
const existingTechnicalNames = Object.keys(this._appStateService.dictionaryData[this._dossierTemplateId]);
|
||||||
const baseTechnicalName = toKebabCase(value.trim());
|
const baseTechnicalName = toSnakeCase(value.trim());
|
||||||
let technicalName = baseTechnicalName;
|
let technicalName = baseTechnicalName;
|
||||||
let suffix = 1;
|
let suffix = 1;
|
||||||
while (existingTechnicalNames.includes(technicalName)) {
|
while (existingTechnicalNames.includes(technicalName)) {
|
||||||
technicalName = [baseTechnicalName, suffix++].join('-');
|
technicalName = [baseTechnicalName, suffix++].join('_');
|
||||||
}
|
}
|
||||||
return technicalName;
|
return technicalName;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -35,11 +35,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="dialog-actions">
|
<div class="dialog-actions">
|
||||||
<button (click)="save()" [disabled]="form.invalid || !changed" color="primary" mat-flat-button type="button">
|
<button (click)="save()" [disabled]="disabled" color="primary" mat-flat-button type="button">
|
||||||
{{ 'add-edit-dossier-attribute.save' | translate }}
|
{{ 'add-edit-dossier-attribute.save' | translate }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<iqser-circle-button class="dialog-close" icon="iqser:close" mat-dialog-close></iqser-circle-button>
|
<iqser-circle-button (action)="close()" class="dialog-close" icon="iqser:close"></iqser-circle-button>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@ -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 { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||||
import { DossierAttributeConfigTypes, FileAttributeConfigTypes, IDossierAttributeConfig } from '@red/domain';
|
import { DossierAttributeConfigTypes, FileAttributeConfigTypes, IDossierAttributeConfig } from '@red/domain';
|
||||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
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 { HttpErrorResponse } from '@angular/common/http';
|
||||||
import { DossierAttributesService } from '@shared/services/controller-wrappers/dossier-attributes.service';
|
import { DossierAttributesService } from '@shared/services/controller-wrappers/dossier-attributes.service';
|
||||||
import { dossierAttributeTypesTranslations } from '../../translations/dossier-attribute-types-translations';
|
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',
|
templateUrl: './add-edit-dossier-attribute-dialog.component.html',
|
||||||
styleUrls: ['./add-edit-dossier-attribute-dialog.component.scss'],
|
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;
|
dossierAttribute: IDossierAttributeConfig = this.data.dossierAttribute;
|
||||||
readonly form: FormGroup = this._getForm(this.dossierAttribute);
|
|
||||||
readonly translations = dossierAttributeTypesTranslations;
|
readonly translations = dossierAttributeTypesTranslations;
|
||||||
readonly typeOptions = Object.keys(DossierAttributeConfigTypes);
|
readonly typeOptions = Object.keys(DossierAttributeConfigTypes);
|
||||||
|
|
||||||
@ -23,11 +22,14 @@ export class AddEditDossierAttributeDialogComponent extends AutoUnsubscribe impl
|
|||||||
private readonly _loadingService: LoadingService,
|
private readonly _loadingService: LoadingService,
|
||||||
private readonly _dossierAttributesService: DossierAttributesService,
|
private readonly _dossierAttributesService: DossierAttributesService,
|
||||||
private readonly _toaster: Toaster,
|
private readonly _toaster: Toaster,
|
||||||
readonly dialogRef: MatDialogRef<AddEditDossierAttributeDialogComponent>,
|
protected readonly _injector: Injector,
|
||||||
|
protected readonly _dialogRef: MatDialogRef<AddEditDossierAttributeDialogComponent>,
|
||||||
@Inject(MAT_DIALOG_DATA)
|
@Inject(MAT_DIALOG_DATA)
|
||||||
readonly data: { readonly dossierAttribute: IDossierAttributeConfig },
|
readonly data: { readonly dossierAttribute: IDossierAttributeConfig },
|
||||||
) {
|
) {
|
||||||
super();
|
super(_injector, _dialogRef);
|
||||||
|
this.form = this._getForm(this.dossierAttribute);
|
||||||
|
this.initialFormValue = this.form.getRawValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
get changed(): boolean {
|
get changed(): boolean {
|
||||||
@ -55,7 +57,7 @@ export class AddEditDossierAttributeDialogComponent extends AutoUnsubscribe impl
|
|||||||
|
|
||||||
this._dossierAttributesService.createOrUpdate(attribute).subscribe(
|
this._dossierAttributesService.createOrUpdate(attribute).subscribe(
|
||||||
() => {
|
() => {
|
||||||
this.dialogRef.close(true);
|
this._dialogRef.close(true);
|
||||||
},
|
},
|
||||||
(error: HttpErrorResponse) => {
|
(error: HttpErrorResponse) => {
|
||||||
this._loadingService.stop();
|
this._loadingService.stop();
|
||||||
|
|||||||
@ -33,16 +33,11 @@
|
|||||||
|
|
||||||
<div class="validity">
|
<div class="validity">
|
||||||
<div>
|
<div>
|
||||||
<mat-checkbox
|
<mat-checkbox (change)="toggleHasValid('from')" [checked]="hasValidFrom" class="filter-menu-checkbox" color="primary">
|
||||||
(change)="hasValidFrom = !hasValidFrom"
|
|
||||||
[checked]="hasValidFrom"
|
|
||||||
class="filter-menu-checkbox"
|
|
||||||
color="primary"
|
|
||||||
>
|
|
||||||
{{ 'add-edit-dossier-template.form.valid-from' | translate }}
|
{{ 'add-edit-dossier-template.form.valid-from' | translate }}
|
||||||
</mat-checkbox>
|
</mat-checkbox>
|
||||||
|
|
||||||
<mat-checkbox (change)="hasValidTo = !hasValidTo" [checked]="hasValidTo" class="filter-menu-checkbox" color="primary">
|
<mat-checkbox (change)="toggleHasValid('to')" [checked]="hasValidTo" class="filter-menu-checkbox" color="primary">
|
||||||
{{ 'add-edit-dossier-template.form.valid-to' | translate }}
|
{{ 'add-edit-dossier-template.form.valid-to' | translate }}
|
||||||
</mat-checkbox>
|
</mat-checkbox>
|
||||||
</div>
|
</div>
|
||||||
@ -87,11 +82,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="dialog-actions">
|
<div class="dialog-actions">
|
||||||
<button (click)="save()" [disabled]="!valid || !changed" color="primary" mat-flat-button type="button">
|
<button (click)="save()" [disabled]="disabled" color="primary" mat-flat-button type="button">
|
||||||
{{ 'add-edit-dossier-template.save' | translate }}
|
{{ 'add-edit-dossier-template.save' | translate }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<iqser-circle-button class="dialog-close" icon="iqser:close" mat-dialog-close></iqser-circle-button>
|
<iqser-circle-button (action)="close()" class="dialog-close" icon="iqser:close"></iqser-circle-button>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@ -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 { 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 { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||||
import * as moment from 'moment';
|
import * as moment from 'moment';
|
||||||
import { 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'],
|
styleUrls: ['./add-edit-dossier-template-dialog.component.scss'],
|
||||||
})
|
})
|
||||||
export class AddEditDossierTemplateDialogComponent extends BaseDialogComponent {
|
export class AddEditDossierTemplateDialogComponent extends BaseDialogComponent {
|
||||||
readonly form: FormGroup = this._getForm();
|
|
||||||
hasValidFrom: boolean;
|
hasValidFrom: boolean;
|
||||||
hasValidTo: boolean;
|
hasValidTo: boolean;
|
||||||
downloadTypesEnum: DownloadFileType[] = ['ORIGINAL', 'PREVIEW', 'REDACTED'];
|
downloadTypesEnum: DownloadFileType[] = ['ORIGINAL', 'PREVIEW', 'REDACTED'];
|
||||||
@ -25,66 +24,50 @@ export class AddEditDossierTemplateDialogComponent extends BaseDialogComponent {
|
|||||||
key: type,
|
key: type,
|
||||||
label: downloadTypesTranslations[type],
|
label: downloadTypesTranslations[type],
|
||||||
}));
|
}));
|
||||||
readonly disabled = false;
|
|
||||||
private _previousValidFrom: Moment;
|
private _previousValidFrom: Moment;
|
||||||
private _previousValidTo: Moment;
|
private _previousValidTo: Moment;
|
||||||
|
private _lastValidFrom: Moment;
|
||||||
|
private _lastValidTo: Moment;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly _appStateService: AppStateService,
|
private readonly _appStateService: AppStateService,
|
||||||
private readonly _toaster: Toaster,
|
private readonly _toaster: Toaster,
|
||||||
private readonly _dossierTemplatesService: DossierTemplatesService,
|
private readonly _dossierTemplatesService: DossierTemplatesService,
|
||||||
private readonly _formBuilder: FormBuilder,
|
private readonly _formBuilder: FormBuilder,
|
||||||
|
protected readonly _injector: Injector,
|
||||||
|
protected readonly _dialogRef: MatDialogRef<AddEditDossierTemplateDialogComponent>,
|
||||||
private readonly _loadingService: LoadingService,
|
private readonly _loadingService: LoadingService,
|
||||||
public dialogRef: MatDialogRef<AddEditDossierTemplateDialogComponent>,
|
|
||||||
@Inject(MAT_DIALOG_DATA) readonly dossierTemplate: IDossierTemplate,
|
@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.hasValidFrom = !!this.dossierTemplate?.validFrom;
|
||||||
this.hasValidTo = !!this.dossierTemplate?.validTo;
|
this.hasValidTo = !!this.dossierTemplate?.validTo;
|
||||||
|
|
||||||
this._previousValidFrom = this.form.get('validFrom').value;
|
this._previousValidFrom = this._lastValidFrom = this.form.get('validFrom').value;
|
||||||
this._previousValidTo = this.form.get('validTo').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._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 {
|
toggleHasValid(extremity: string) {
|
||||||
return this.form.valid;
|
if (extremity === 'from') {
|
||||||
}
|
this.hasValidFrom = !this.hasValidFrom;
|
||||||
|
this.form.controls['validFrom'].setValue(this.hasValidFrom ? this._lastValidFrom : null);
|
||||||
get changed(): boolean {
|
} else {
|
||||||
if (!this.dossierTemplate) {
|
this.hasValidTo = !this.hasValidTo;
|
||||||
return true;
|
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() {
|
async save() {
|
||||||
@ -95,10 +78,10 @@ export class AddEditDossierTemplateDialogComponent extends BaseDialogComponent {
|
|||||||
...this.form.getRawValue(),
|
...this.form.getRawValue(),
|
||||||
validFrom: this.hasValidFrom ? this.form.get('validFrom').value : null,
|
validFrom: this.hasValidFrom ? this.form.get('validFrom').value : null,
|
||||||
validTo: this.hasValidTo ? this.form.get('validTo').value : null,
|
validTo: this.hasValidTo ? this.form.get('validTo').value : null,
|
||||||
};
|
} as IDossierTemplate;
|
||||||
await this._dossierTemplatesService.createOrUpdate(dossierTemplate).toPromise();
|
await this._dossierTemplatesService.createOrUpdate(dossierTemplate).toPromise();
|
||||||
await this._appStateService.loadDictionaryData();
|
await this._appStateService.loadDictionaryData();
|
||||||
this.dialogRef.close(true);
|
this._dialogRef.close(true);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
const message =
|
const message =
|
||||||
error.status === HttpStatusCode.Conflict
|
error.status === HttpStatusCode.Conflict
|
||||||
@ -136,7 +119,7 @@ export class AddEditDossierTemplateDialogComponent extends BaseDialogComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _requiredIfValidator(predicate) {
|
private _requiredIfValidator(predicate) {
|
||||||
return formControl => {
|
return (formControl: AbstractControl) => {
|
||||||
if (!formControl.parent) {
|
if (!formControl.parent) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -84,11 +84,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="dialog-actions">
|
<div class="dialog-actions">
|
||||||
<button (click)="save()" [disabled]="!valid || !changed" color="primary" mat-flat-button type="button">
|
<button (click)="save()" [disabled]="disabled" color="primary" mat-flat-button type="button">
|
||||||
{{ 'add-edit-file-attribute.save' | translate }}
|
{{ 'add-edit-file-attribute.save' | translate }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<iqser-circle-button class="dialog-close" icon="iqser:close" mat-dialog-close></iqser-circle-button>
|
<iqser-circle-button (action)="close()" class="dialog-close" icon="iqser:close"></iqser-circle-button>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@ -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 { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||||
import { FileAttributeConfigTypes, IFileAttributeConfig } from '@red/domain';
|
import { FileAttributeConfigTypes, IFileAttributeConfig } from '@red/domain';
|
||||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||||
import { fileAttributeTypesTranslations } from '../../translations/file-attribute-types-translations';
|
import { fileAttributeTypesTranslations } from '../../translations/file-attribute-types-translations';
|
||||||
import { FileAttributesService } from '@services/entity-services/file-attributes.service';
|
import { FileAttributesService } from '@services/entity-services/file-attributes.service';
|
||||||
import { BaseDialogComponent } from '@iqser/common-ui';
|
import { BaseDialogComponent } from '../../../../../../../../libs/common-ui/src';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'redaction-add-edit-file-attribute-dialog',
|
selector: 'redaction-add-edit-file-attribute-dialog',
|
||||||
@ -13,12 +13,10 @@ import { BaseDialogComponent } from '@iqser/common-ui';
|
|||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class AddEditFileAttributeDialogComponent extends BaseDialogComponent {
|
export class AddEditFileAttributeDialogComponent extends BaseDialogComponent {
|
||||||
readonly disabled = false;
|
|
||||||
DISPLAYED_FILTERABLE_LIMIT = 3;
|
DISPLAYED_FILTERABLE_LIMIT = 3;
|
||||||
translations = fileAttributeTypesTranslations;
|
translations = fileAttributeTypesTranslations;
|
||||||
fileAttribute: IFileAttributeConfig = this.data.fileAttribute;
|
fileAttribute: IFileAttributeConfig = this.data.fileAttribute;
|
||||||
dossierTemplateId: string = this.data.dossierTemplateId;
|
dossierTemplateId: string = this.data.dossierTemplateId;
|
||||||
readonly form!: FormGroup;
|
|
||||||
readonly typeOptions = Object.keys(FileAttributeConfigTypes);
|
readonly typeOptions = Object.keys(FileAttributeConfigTypes);
|
||||||
readonly canSetDisplayed!: boolean;
|
readonly canSetDisplayed!: boolean;
|
||||||
readonly canSetFilterable!: boolean;
|
readonly canSetFilterable!: boolean;
|
||||||
@ -26,7 +24,8 @@ export class AddEditFileAttributeDialogComponent extends BaseDialogComponent {
|
|||||||
constructor(
|
constructor(
|
||||||
private readonly _formBuilder: FormBuilder,
|
private readonly _formBuilder: FormBuilder,
|
||||||
private readonly _fileAttributesService: FileAttributesService,
|
private readonly _fileAttributesService: FileAttributesService,
|
||||||
public dialogRef: MatDialogRef<AddEditFileAttributeDialogComponent>,
|
protected readonly _injector: Injector,
|
||||||
|
protected readonly _dialogRef: MatDialogRef<AddEditFileAttributeDialogComponent>,
|
||||||
@Inject(MAT_DIALOG_DATA)
|
@Inject(MAT_DIALOG_DATA)
|
||||||
public data: {
|
public data: {
|
||||||
fileAttribute: IFileAttributeConfig;
|
fileAttribute: IFileAttributeConfig;
|
||||||
@ -35,32 +34,11 @@ export class AddEditFileAttributeDialogComponent extends BaseDialogComponent {
|
|||||||
numberOfFilterableAttrs: number;
|
numberOfFilterableAttrs: number;
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
super();
|
super(_injector, _dialogRef);
|
||||||
this.canSetDisplayed = data.numberOfDisplayedAttrs < this.DISPLAYED_FILTERABLE_LIMIT || data.fileAttribute?.displayedInFileList;
|
this.canSetDisplayed = data.numberOfDisplayedAttrs < this.DISPLAYED_FILTERABLE_LIMIT || data.fileAttribute?.displayedInFileList;
|
||||||
this.canSetFilterable = data.numberOfFilterableAttrs < this.DISPLAYED_FILTERABLE_LIMIT || data.fileAttribute?.filterable;
|
this.canSetFilterable = data.numberOfFilterableAttrs < this.DISPLAYED_FILTERABLE_LIMIT || data.fileAttribute?.filterable;
|
||||||
this.form = this._getForm(this.fileAttribute);
|
this.form = this._getForm(this.fileAttribute);
|
||||||
}
|
this.initialFormValue = this.form.getRawValue();
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
save() {
|
save() {
|
||||||
@ -69,7 +47,7 @@ export class AddEditFileAttributeDialogComponent extends BaseDialogComponent {
|
|||||||
editable: !this.form.get('readonly').value,
|
editable: !this.form.get('readonly').value,
|
||||||
...this.form.getRawValue(),
|
...this.form.getRawValue(),
|
||||||
};
|
};
|
||||||
this.dialogRef.close(fileAttribute);
|
this._dialogRef.close(fileAttribute);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _getForm(fileAttribute: IFileAttributeConfig): FormGroup {
|
private _getForm(fileAttribute: IFileAttributeConfig): FormGroup {
|
||||||
|
|||||||
@ -134,13 +134,14 @@ export class UserDetailsComponent extends AutoUnsubscribe implements OnChanges,
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _getForm(): FormGroup {
|
private _getForm(): FormGroup {
|
||||||
|
console.log(this.user);
|
||||||
return this._formBuilder.group({
|
return this._formBuilder.group({
|
||||||
firstName: [this.user?.firstName, Validators.required],
|
firstName: [this.user?.firstName, Validators.required],
|
||||||
lastName: [this.user?.lastName, Validators.required],
|
lastName: [this.user?.lastName, Validators.required],
|
||||||
email: [
|
email: [
|
||||||
{
|
{
|
||||||
value: this.user?.email,
|
value: this.user?.email,
|
||||||
disabled: !!this.user,
|
disabled: !!this.user?.email,
|
||||||
},
|
},
|
||||||
[Validators.required, Validators.email],
|
[Validators.required, Validators.email],
|
||||||
],
|
],
|
||||||
|
|||||||
@ -28,11 +28,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="dialog-actions">
|
<div class="dialog-actions">
|
||||||
<button (click)="save()" [disabled]="!valid || !changed" color="primary" mat-flat-button type="button">
|
<button (click)="save()" [disabled]="disabled" color="primary" mat-flat-button type="button">
|
||||||
{{ 'edit-color-dialog.save' | translate }}
|
{{ 'edit-color-dialog.save' | translate }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<iqser-circle-button class="dialog-close" icon="iqser:close" mat-dialog-close></iqser-circle-button>
|
<iqser-circle-button (action)="close()" class="dialog-close" icon="iqser:close"></iqser-circle-button>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@ -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 { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||||
import { DefaultColorType, IColors } from '@red/domain';
|
import { DefaultColorType, IColors } from '@red/domain';
|
||||||
import { BaseDialogComponent, Toaster } from '@iqser/common-ui';
|
import { BaseDialogComponent, Toaster } from '@iqser/common-ui';
|
||||||
@ -19,10 +19,7 @@ interface IEditColorData {
|
|||||||
styleUrls: ['./edit-color-dialog.component.scss'],
|
styleUrls: ['./edit-color-dialog.component.scss'],
|
||||||
})
|
})
|
||||||
export class EditColorDialogComponent extends BaseDialogComponent {
|
export class EditColorDialogComponent extends BaseDialogComponent {
|
||||||
readonly form: FormGroup;
|
|
||||||
translations = defaultColorsTranslations;
|
translations = defaultColorsTranslations;
|
||||||
readonly disabled = false;
|
|
||||||
private readonly _initialColor: string;
|
|
||||||
private readonly _dossierTemplateId: string;
|
private readonly _dossierTemplateId: string;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@ -30,23 +27,16 @@ export class EditColorDialogComponent extends BaseDialogComponent {
|
|||||||
private readonly _dictionaryService: DictionaryService,
|
private readonly _dictionaryService: DictionaryService,
|
||||||
private readonly _toaster: Toaster,
|
private readonly _toaster: Toaster,
|
||||||
private readonly _translateService: TranslateService,
|
private readonly _translateService: TranslateService,
|
||||||
private readonly _dialogRef: MatDialogRef<EditColorDialogComponent>,
|
protected readonly _injector: Injector,
|
||||||
|
protected readonly _dialogRef: MatDialogRef<EditColorDialogComponent>,
|
||||||
@Inject(MAT_DIALOG_DATA)
|
@Inject(MAT_DIALOG_DATA)
|
||||||
readonly data: IEditColorData,
|
readonly data: IEditColorData,
|
||||||
) {
|
) {
|
||||||
super();
|
super(_injector, _dialogRef);
|
||||||
this._dossierTemplateId = data.dossierTemplateId;
|
this._dossierTemplateId = data.dossierTemplateId;
|
||||||
this._initialColor = data.colors[data.colorKey];
|
|
||||||
|
|
||||||
this.form = this._getForm();
|
this.form = this._getForm();
|
||||||
}
|
this.initialFormValue = this.form.getRawValue();
|
||||||
|
|
||||||
get changed(): boolean {
|
|
||||||
return this.form.get('color').value !== this._initialColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
get valid(): boolean {
|
|
||||||
return this.form.valid;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async save() {
|
async save() {
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
|||||||
import { UserService } from '@services/user.service';
|
import { UserService } from '@services/user.service';
|
||||||
import { RouterHistoryService } from '@services/router-history.service';
|
import { RouterHistoryService } from '@services/router-history.service';
|
||||||
import { DigitalSignatureService } from '../../services/digital-signature.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';
|
import { HttpStatusCode } from '@angular/common/http';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -40,29 +40,30 @@ export class DigitalSignatureScreenComponent extends AutoUnsubscribe implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
saveDigitalSignature() {
|
saveDigitalSignature() {
|
||||||
const digitalSignature = {
|
const formValue = this.form.getRawValue();
|
||||||
...this.form.getRawValue(),
|
const digitalSignature: IDigitalSignature = {
|
||||||
|
...formValue,
|
||||||
};
|
};
|
||||||
//adjusted for chrome auto-complete / password manager
|
//adjusted for chrome auto-complete / password manager
|
||||||
digitalSignature.password = digitalSignature.keySecret;
|
digitalSignature.password = formValue.keySecret;
|
||||||
|
|
||||||
const observable = this.digitalSignatureExists
|
const observable = this.digitalSignatureExists
|
||||||
? this._digitalSignatureService.update(digitalSignature)
|
? this._digitalSignatureService.update(digitalSignature)
|
||||||
: this._digitalSignatureService.save(digitalSignature);
|
: this._digitalSignatureService.save(digitalSignature);
|
||||||
|
|
||||||
this.addSubscription = observable.subscribe(
|
this.addSubscription = observable.subscribe({
|
||||||
() => {
|
next: () => {
|
||||||
this.loadDigitalSignatureAndInitializeForm();
|
this.loadDigitalSignatureAndInitializeForm();
|
||||||
this._toaster.success(_('digital-signature-screen.action.save-success'));
|
this._toaster.success(_('digital-signature-screen.action.save-success'));
|
||||||
},
|
},
|
||||||
error => {
|
error: error => {
|
||||||
if (error.status === HttpStatusCode.BadRequest) {
|
if (error.status === HttpStatusCode.BadRequest) {
|
||||||
this._toaster.error(_('digital-signature-screen.action.certificate-not-valid-error'));
|
this._toaster.error(_('digital-signature-screen.action.certificate-not-valid-error'));
|
||||||
} else {
|
} else {
|
||||||
this._toaster.error(_('digital-signature-screen.action.save-error'));
|
this._toaster.error(_('digital-signature-screen.action.save-error'));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
removeDigitalSignature() {
|
removeDigitalSignature() {
|
||||||
@ -85,23 +86,23 @@ export class DigitalSignatureScreenComponent extends AutoUnsubscribe implements
|
|||||||
this.form.get('certificateName').setValue(file.name);
|
this.form.get('certificateName').setValue(file.name);
|
||||||
input.value = null;
|
input.value = null;
|
||||||
};
|
};
|
||||||
fileReader.readAsDataURL(file);
|
fileReader.readAsDataURL(file as Blob);
|
||||||
}
|
}
|
||||||
|
|
||||||
loadDigitalSignatureAndInitializeForm() {
|
loadDigitalSignatureAndInitializeForm() {
|
||||||
this._loadingService.start();
|
this._loadingService.start();
|
||||||
this.addSubscription = this._digitalSignatureService
|
this._digitalSignatureService
|
||||||
.getSignature()
|
.getSignature()
|
||||||
.subscribe(
|
.subscribe({
|
||||||
digitalSignature => {
|
next: digitalSignature => {
|
||||||
this.digitalSignatureExists = true;
|
this.digitalSignatureExists = true;
|
||||||
this.digitalSignature = digitalSignature;
|
this.digitalSignature = digitalSignature;
|
||||||
},
|
},
|
||||||
() => {
|
error: () => {
|
||||||
this.digitalSignatureExists = false;
|
this.digitalSignatureExists = false;
|
||||||
this.digitalSignature = {};
|
this.digitalSignature = {};
|
||||||
},
|
},
|
||||||
)
|
})
|
||||||
.add(() => {
|
.add(() => {
|
||||||
this.form = this._getForm();
|
this.form = this._getForm();
|
||||||
this._loadingService.stop();
|
this._loadingService.stop();
|
||||||
|
|||||||
@ -11,7 +11,7 @@
|
|||||||
<ng-template #bulkActions>
|
<ng-template #bulkActions>
|
||||||
<iqser-circle-button
|
<iqser-circle-button
|
||||||
(action)="openConfirmDeleteDialog()"
|
(action)="openConfirmDeleteDialog()"
|
||||||
*ngIf="userService.currentUser.isAdmin && listingService.areSomeSelected$ | async"
|
*ngIf="userPreferenceService.areDevFeaturesEnabled && userService.currentUser.isAdmin && listingService.areSomeSelected$ | async"
|
||||||
[tooltip]="'justifications-listing.bulk.delete' | translate"
|
[tooltip]="'justifications-listing.bulk.delete' | translate"
|
||||||
[type]="circleButtonTypes.dark"
|
[type]="circleButtonTypes.dark"
|
||||||
icon="iqser:trash"
|
icon="iqser:trash"
|
||||||
@ -26,7 +26,7 @@
|
|||||||
<div class="table-header-actions">
|
<div class="table-header-actions">
|
||||||
<iqser-icon-button
|
<iqser-icon-button
|
||||||
(action)="openAddJustificationDialog()"
|
(action)="openAddJustificationDialog()"
|
||||||
*ngIf="userService.currentUser.isAdmin"
|
*ngIf="userPreferenceService.areDevFeaturesEnabled && userService.currentUser.isAdmin"
|
||||||
[label]="'justifications-listing.add-new' | translate"
|
[label]="'justifications-listing.add-new' | translate"
|
||||||
[type]="iconButtonTypes.primary"
|
[type]="iconButtonTypes.primary"
|
||||||
icon="iqser:plus"
|
icon="iqser:plus"
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import { JustificationsService } from '@services/entity-services/justifications.
|
|||||||
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
|
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
|
||||||
import { JustificationsDialogService } from '../justifications-dialog.service';
|
import { JustificationsDialogService } from '../justifications-dialog.service';
|
||||||
import { UserService } from '@services/user.service';
|
import { UserService } from '@services/user.service';
|
||||||
|
import { UserPreferenceService } from '@services/user-preference.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'redaction-justifications-screen',
|
selector: 'redaction-justifications-screen',
|
||||||
@ -43,6 +44,7 @@ export class JustificationsScreenComponent extends ListingComponent<Justificatio
|
|||||||
private readonly _loadingService: LoadingService,
|
private readonly _loadingService: LoadingService,
|
||||||
private readonly _dossierTemplatesService: DossierTemplatesService,
|
private readonly _dossierTemplatesService: DossierTemplatesService,
|
||||||
private readonly _dialogService: JustificationsDialogService,
|
private readonly _dialogService: JustificationsDialogService,
|
||||||
|
readonly userPreferenceService: UserPreferenceService,
|
||||||
readonly userService: UserService,
|
readonly userService: UserService,
|
||||||
) {
|
) {
|
||||||
super(_injector);
|
super(_injector);
|
||||||
|
|||||||
@ -24,7 +24,7 @@
|
|||||||
|
|
||||||
<iqser-circle-button
|
<iqser-circle-button
|
||||||
(action)="openConfirmDeleteDialog()"
|
(action)="openConfirmDeleteDialog()"
|
||||||
*ngIf="userService.currentUser.isAdmin"
|
*ngIf="userPreferenceService.areDevFeaturesEnabled && userService.currentUser.isAdmin"
|
||||||
[tooltip]="'justifications-listing.actions.delete' | translate"
|
[tooltip]="'justifications-listing.actions.delete' | translate"
|
||||||
[type]="circleButtonTypes.dark"
|
[type]="circleButtonTypes.dark"
|
||||||
icon="iqser:trash"
|
icon="iqser:trash"
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { Justification } from '@red/domain';
|
|||||||
import { CircleButtonTypes, ListingService, LoadingService } from '@iqser/common-ui';
|
import { CircleButtonTypes, ListingService, LoadingService } from '@iqser/common-ui';
|
||||||
import { JustificationsDialogService } from '../justifications-dialog.service';
|
import { JustificationsDialogService } from '../justifications-dialog.service';
|
||||||
import { UserService } from '@services/user.service';
|
import { UserService } from '@services/user.service';
|
||||||
|
import { UserPreferenceService } from '@services/user-preference.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'redaction-table-item',
|
selector: 'redaction-table-item',
|
||||||
@ -18,6 +19,7 @@ export class TableItemComponent {
|
|||||||
private readonly _dialogService: JustificationsDialogService,
|
private readonly _dialogService: JustificationsDialogService,
|
||||||
private readonly _loadingService: LoadingService,
|
private readonly _loadingService: LoadingService,
|
||||||
private readonly _listingService: ListingService<Justification>,
|
private readonly _listingService: ListingService<Justification>,
|
||||||
|
readonly userPreferenceService: UserPreferenceService,
|
||||||
readonly userService: UserService,
|
readonly userService: UserService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import { UserService } from '@services/user.service';
|
|||||||
import { RouterHistoryService } from '@services/router-history.service';
|
import { RouterHistoryService } from '@services/router-history.service';
|
||||||
import { LicenseReportService } from '../../services/licence-report.service';
|
import { LicenseReportService } from '../../services/licence-report.service';
|
||||||
import { ILicenseReport } from '@red/domain';
|
import { ILicenseReport } from '@red/domain';
|
||||||
|
import { Color, ScaleType } from '@swimlane/ngx-charts';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'redaction-license-information-screen',
|
selector: 'redaction-license-information-screen',
|
||||||
@ -31,14 +32,16 @@ export class LicenseInformationScreenComponent implements OnInit {
|
|||||||
analysisPercentageOfLicense = 100;
|
analysisPercentageOfLicense = 100;
|
||||||
barChart: any[];
|
barChart: any[];
|
||||||
lineChartSeries: any[] = [];
|
lineChartSeries: any[] = [];
|
||||||
lineChartScheme = {
|
lineChartScheme: Color = {
|
||||||
|
name: 'Line chart scheme',
|
||||||
selectable: true,
|
selectable: true,
|
||||||
group: 'Ordinal',
|
group: ScaleType.Ordinal,
|
||||||
domain: ['#dd4d50', '#5ce594', '#0389ec'],
|
domain: ['#dd4d50', '#5ce594', '#0389ec'],
|
||||||
};
|
};
|
||||||
comboBarScheme = {
|
comboBarScheme: Color = {
|
||||||
|
name: 'Combo bar scheme',
|
||||||
selectable: true,
|
selectable: true,
|
||||||
group: 'Ordinal',
|
group: ScaleType.Ordinal,
|
||||||
domain: ['#0389ec'],
|
domain: ['#0389ec'],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -92,6 +92,31 @@ export class ReportsScreenComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const dossierTemplateId = this._dossierTemplatesService.activeDossierTemplateId;
|
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)) {
|
if (this._isExcelFile(file)) {
|
||||||
const data = new ConfirmationDialogInput({
|
const data = new ConfirmationDialogInput({
|
||||||
title: _('confirmation-dialog.upload-report-template.title'),
|
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._reportTemplateService.uploadTemplateForm(dossierTemplateId, false, file).toPromise();
|
||||||
await this._loadReportTemplates();
|
await this._loadReportTemplates();
|
||||||
}
|
}
|
||||||
this._fileInput.nativeElement.value = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _deleteTemplate(template: IReportTemplate) {
|
private async _deleteTemplate(template: IReportTemplate) {
|
||||||
|
|||||||
@ -34,6 +34,7 @@ export class AdminDialogService extends DialogService<DialogType> {
|
|||||||
protected readonly _config: DialogConfig<DialogType> = {
|
protected readonly _config: DialogConfig<DialogType> = {
|
||||||
confirm: {
|
confirm: {
|
||||||
component: ConfirmationDialogComponent,
|
component: ConfirmationDialogComponent,
|
||||||
|
dialogConfig: { disableClose: false },
|
||||||
},
|
},
|
||||||
addEditDictionary: {
|
addEditDictionary: {
|
||||||
component: AddEditDictionaryDialogComponent,
|
component: AddEditDictionaryDialogComponent,
|
||||||
@ -49,18 +50,19 @@ export class AdminDialogService extends DialogService<DialogType> {
|
|||||||
},
|
},
|
||||||
deleteFileAttribute: {
|
deleteFileAttribute: {
|
||||||
component: ConfirmDeleteFileAttributeDialogComponent,
|
component: ConfirmDeleteFileAttributeDialogComponent,
|
||||||
|
dialogConfig: { disableClose: false },
|
||||||
},
|
},
|
||||||
importFileAttributes: {
|
importFileAttributes: {
|
||||||
component: FileAttributesCsvImportDialogComponent,
|
component: FileAttributesCsvImportDialogComponent,
|
||||||
dialogConfig: largeDialogConfig,
|
dialogConfig: { ...largeDialogConfig, ...{ disableClose: false } },
|
||||||
},
|
},
|
||||||
deleteUsers: {
|
deleteUsers: {
|
||||||
component: ConfirmDeleteUsersDialogComponent,
|
component: ConfirmDeleteUsersDialogComponent,
|
||||||
dialogConfig: { autoFocus: true },
|
dialogConfig: { autoFocus: true, disableClose: false },
|
||||||
},
|
},
|
||||||
addEditUser: {
|
addEditUser: {
|
||||||
component: AddEditUserDialogComponent,
|
component: AddEditUserDialogComponent,
|
||||||
dialogConfig: { autoFocus: true },
|
dialogConfig: { autoFocus: true, disableClose: false },
|
||||||
},
|
},
|
||||||
smtpAuthConfig: {
|
smtpAuthConfig: {
|
||||||
component: SmtpAuthDialogComponent,
|
component: SmtpAuthDialogComponent,
|
||||||
|
|||||||
@ -28,6 +28,7 @@ export class RedRoleGuard implements CanActivate {
|
|||||||
if (
|
if (
|
||||||
this._userService.currentUser.isUserAdmin &&
|
this._userService.currentUser.isUserAdmin &&
|
||||||
!this._userService.currentUser.isAdmin &&
|
!this._userService.currentUser.isAdmin &&
|
||||||
|
!this._userService.currentUser.isUser &&
|
||||||
!(state.url.startsWith('/main/admin/users') || state.url.startsWith('/main/my-profile'))
|
!(state.url.startsWith('/main/admin/users') || state.url.startsWith('/main/my-profile'))
|
||||||
) {
|
) {
|
||||||
this._router.navigate(['/main/admin/users']);
|
this._router.navigate(['/main/admin/users']);
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<section class="dialog">
|
<section class="dialog">
|
||||||
<form (submit)="saveDossier()" [formGroup]="form">
|
<form (submit)="save()" [formGroup]="form">
|
||||||
<div class="dialog-header heading-l" translate="add-dossier-dialog.header-new"></div>
|
<div class="dialog-header heading-l" translate="add-dossier-dialog.header-new"></div>
|
||||||
|
|
||||||
<div class="dialog-content">
|
<div class="dialog-content">
|
||||||
@ -91,7 +91,7 @@
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<iqser-icon-button
|
<iqser-icon-button
|
||||||
(action)="saveDossier(true)"
|
(action)="save({ addMembers: true })"
|
||||||
[disabled]="disabled"
|
[disabled]="disabled"
|
||||||
[label]="'add-dossier-dialog.actions.save-and-add-members' | translate"
|
[label]="'add-dossier-dialog.actions.save-and-add-members' | translate"
|
||||||
[type]="iconButtonTypes.dark"
|
[type]="iconButtonTypes.dark"
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component, Injector } from '@angular/core';
|
||||||
import { MatDialogRef } from '@angular/material/dialog';
|
import { MatDialogRef } from '@angular/material/dialog';
|
||||||
import { DownloadFileType, IDossierRequest, IDossierTemplate, IReportTemplate } from '@red/domain';
|
import { DownloadFileType, IDossierRequest, IDossierTemplate, IReportTemplate } from '@red/domain';
|
||||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||||
import * as moment from 'moment';
|
import * as moment from 'moment';
|
||||||
import { downloadTypesTranslations } from '../../../../translations/download-types-translations';
|
import { downloadTypesTranslations } from '../../../../translations/download-types-translations';
|
||||||
import { IconButtonTypes } from '@iqser/common-ui';
|
import { BaseDialogComponent, IconButtonTypes, SaveOptions } from '@iqser/common-ui';
|
||||||
import { DossiersService } from '@services/entity-services/dossiers.service';
|
import { DossiersService } from '@services/entity-services/dossiers.service';
|
||||||
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
|
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
|
||||||
import { ReportTemplateService } from '@services/report-template.service';
|
import { ReportTemplateService } from '@services/report-template.service';
|
||||||
@ -13,10 +13,9 @@ import { ReportTemplateService } from '@services/report-template.service';
|
|||||||
templateUrl: './add-dossier-dialog.component.html',
|
templateUrl: './add-dossier-dialog.component.html',
|
||||||
styleUrls: ['./add-dossier-dialog.component.scss'],
|
styleUrls: ['./add-dossier-dialog.component.scss'],
|
||||||
})
|
})
|
||||||
export class AddDossierDialogComponent {
|
export class AddDossierDialogComponent extends BaseDialogComponent {
|
||||||
readonly iconButtonTypes = IconButtonTypes;
|
readonly iconButtonTypes = IconButtonTypes;
|
||||||
|
|
||||||
readonly form: FormGroup;
|
|
||||||
hasDueDate = false;
|
hasDueDate = false;
|
||||||
downloadTypes: { key: DownloadFileType; label: string }[] = ['ORIGINAL', 'PREVIEW', 'REDACTED'].map((type: DownloadFileType) => ({
|
downloadTypes: { key: DownloadFileType; label: string }[] = ['ORIGINAL', 'PREVIEW', 'REDACTED'].map((type: DownloadFileType) => ({
|
||||||
key: type,
|
key: type,
|
||||||
@ -30,10 +29,13 @@ export class AddDossierDialogComponent {
|
|||||||
private readonly _dossierTemplatesService: DossierTemplatesService,
|
private readonly _dossierTemplatesService: DossierTemplatesService,
|
||||||
private readonly _formBuilder: FormBuilder,
|
private readonly _formBuilder: FormBuilder,
|
||||||
private readonly _reportTemplateController: ReportTemplateService,
|
private readonly _reportTemplateController: ReportTemplateService,
|
||||||
readonly dialogRef: MatDialogRef<AddDossierDialogComponent>,
|
protected readonly _injector: Injector,
|
||||||
|
protected readonly _dialogRef: MatDialogRef<AddDossierDialogComponent>,
|
||||||
) {
|
) {
|
||||||
|
super(_injector, _dialogRef);
|
||||||
this._getDossierTemplates();
|
this._getDossierTemplates();
|
||||||
this.form = this._getForm();
|
this.form = this._getForm();
|
||||||
|
this.initialFormValue = this.form.getRawValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
private _getForm(): FormGroup {
|
private _getForm(): FormGroup {
|
||||||
@ -75,10 +77,10 @@ export class AddDossierDialogComponent {
|
|||||||
|
|
||||||
reportTemplateValueMapper = (reportTemplate: IReportTemplate) => reportTemplate.templateId;
|
reportTemplateValueMapper = (reportTemplate: IReportTemplate) => reportTemplate.templateId;
|
||||||
|
|
||||||
async saveDossier(addMembers = false) {
|
async save(options?: SaveOptions) {
|
||||||
const savedDossier = await this._dossiersService.createOrUpdate(this._formToObject()).toPromise();
|
const savedDossier = await this._dossiersService.createOrUpdate(this._formToObject()).toPromise();
|
||||||
if (savedDossier) {
|
if (savedDossier) {
|
||||||
this.dialogRef.close({ dossier: savedDossier, addMembers });
|
this._dialogRef.close({ dossier: savedDossier, addMembers: options?.addMembers });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -38,12 +38,12 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="dialog-actions">
|
<div class="dialog-actions">
|
||||||
<button [disabled]="!form.valid || !changed" color="primary" mat-flat-button type="submit">
|
<button [disabled]="disabled" color="primary" mat-flat-button type="submit">
|
||||||
{{ 'change-legal-basis-dialog.actions.save' | translate }}
|
{{ 'change-legal-basis-dialog.actions.save' | translate }}
|
||||||
</button>
|
</button>
|
||||||
<div class="all-caps-label cancel" mat-dialog-close translate="change-legal-basis-dialog.actions.cancel"></div>
|
<div class="all-caps-label cancel" mat-dialog-close translate="change-legal-basis-dialog.actions.cancel"></div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<iqser-circle-button class="dialog-close" icon="iqser:close" mat-dialog-close></iqser-circle-button>
|
<iqser-circle-button class="dialog-close" icon="iqser:close" (action)="close()"></iqser-circle-button>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@ -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 { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||||
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
||||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
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 { DossiersService } from '@services/entity-services/dossiers.service';
|
||||||
import { JustificationsService } from '@services/entity-services/justifications.service';
|
import { JustificationsService } from '@services/entity-services/justifications.service';
|
||||||
import { Dossier } from '@red/domain';
|
import { Dossier } from '@red/domain';
|
||||||
|
import { BaseDialogComponent } from '@iqser/common-ui';
|
||||||
|
|
||||||
export interface LegalBasisOption {
|
export interface LegalBasisOption {
|
||||||
label?: string;
|
label?: string;
|
||||||
@ -16,8 +17,7 @@ export interface LegalBasisOption {
|
|||||||
@Component({
|
@Component({
|
||||||
templateUrl: './change-legal-basis-dialog.component.html',
|
templateUrl: './change-legal-basis-dialog.component.html',
|
||||||
})
|
})
|
||||||
export class ChangeLegalBasisDialogComponent implements OnInit {
|
export class ChangeLegalBasisDialogComponent extends BaseDialogComponent implements OnInit {
|
||||||
form: FormGroup = this._getForm();
|
|
||||||
isDocumentAdmin: boolean;
|
isDocumentAdmin: boolean;
|
||||||
legalOptions: LegalBasisOption[] = [];
|
legalOptions: LegalBasisOption[] = [];
|
||||||
|
|
||||||
@ -26,16 +26,12 @@ export class ChangeLegalBasisDialogComponent implements OnInit {
|
|||||||
private readonly _dossiersService: DossiersService,
|
private readonly _dossiersService: DossiersService,
|
||||||
private readonly _permissionsService: PermissionsService,
|
private readonly _permissionsService: PermissionsService,
|
||||||
private readonly _formBuilder: FormBuilder,
|
private readonly _formBuilder: FormBuilder,
|
||||||
readonly dialogRef: MatDialogRef<ChangeLegalBasisDialogComponent>,
|
protected readonly _injector: Injector,
|
||||||
|
protected readonly _dialogRef: MatDialogRef<ChangeLegalBasisDialogComponent>,
|
||||||
@Inject(MAT_DIALOG_DATA) private readonly _data: { annotations: AnnotationWrapper[]; dossier: Dossier },
|
@Inject(MAT_DIALOG_DATA) private readonly _data: { annotations: AnnotationWrapper[]; dossier: Dossier },
|
||||||
) {}
|
) {
|
||||||
|
super(_injector, _dialogRef);
|
||||||
get changed(): boolean {
|
this.form = this._getForm();
|
||||||
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
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get allRectangles(): boolean {
|
get allRectangles(): boolean {
|
||||||
@ -43,6 +39,7 @@ export class ChangeLegalBasisDialogComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
|
super.ngOnInit();
|
||||||
const data = await this._justificationsService.getForDossierTemplate(this._data.dossier.dossierTemplateId).toPromise();
|
const data = await this._justificationsService.getForDossierTemplate(this._data.dossier.dossierTemplateId).toPromise();
|
||||||
|
|
||||||
this.legalOptions = data
|
this.legalOptions = data
|
||||||
@ -56,6 +53,7 @@ export class ChangeLegalBasisDialogComponent implements OnInit {
|
|||||||
this.form.patchValue({
|
this.form.patchValue({
|
||||||
reason: this.legalOptions.find(option => option.legalBasis === this._data.annotations[0].legalBasis),
|
reason: this.legalOptions.find(option => option.legalBasis === this._data.annotations[0].legalBasis),
|
||||||
});
|
});
|
||||||
|
this.initialFormValue = this.form.getRawValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
private _getForm(): FormGroup {
|
private _getForm(): FormGroup {
|
||||||
@ -69,7 +67,7 @@ export class ChangeLegalBasisDialogComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
save() {
|
save() {
|
||||||
this.dialogRef.close({
|
this._dialogRef.close({
|
||||||
legalBasis: this.form.get('reason').value.legalBasis,
|
legalBasis: this.form.get('reason').value.legalBasis,
|
||||||
section: this.form.get('section').value,
|
section: this.form.get('section').value,
|
||||||
comment: this.form.get('comment').value,
|
comment: this.form.get('comment').value,
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
<section *ngIf="!!form" class="dialog">
|
<section *ngIf="!!form" class="dialog">
|
||||||
<div class="dialog-header heading-l" translate="document-info.title"></div>
|
<div class="dialog-header heading-l" translate="document-info.title"></div>
|
||||||
|
|
||||||
<form (submit)="saveDocumentInfo()" [formGroup]="form">
|
<form (submit)="save()" [formGroup]="form">
|
||||||
<div class="dialog-content">
|
<div class="dialog-content">
|
||||||
<div *ngFor="let attr of attributes" class="iqser-input-group w-300">
|
<div *ngFor="let attr of attributes" class="iqser-input-group w-300">
|
||||||
<label>{{ attr.label }}</label>
|
<label>{{ attr.label }}</label>
|
||||||
@ -9,11 +9,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="dialog-actions">
|
<div class="dialog-actions">
|
||||||
<button [disabled]="form.invalid" color="primary" mat-flat-button type="submit">
|
<button [disabled]="disabled" color="primary" mat-flat-button type="submit">
|
||||||
{{ 'document-info.save' | translate }}
|
{{ 'document-info.save' | translate }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<iqser-circle-button class="dialog-close" icon="iqser:close" mat-dialog-close></iqser-circle-button>
|
<iqser-circle-button class="dialog-close" icon="iqser:close" (action)="close()"></iqser-circle-button>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@ -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 { FormBuilder, FormGroup } from '@angular/forms';
|
||||||
import { Dossier, File, IFileAttributeConfig } from '@red/domain';
|
import { Dossier, File, IFileAttributeConfig } from '@red/domain';
|
||||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||||
import { FileAttributesService } from '@services/entity-services/file-attributes.service';
|
import { FileAttributesService } from '@services/entity-services/file-attributes.service';
|
||||||
import { DossiersService } from '@services/entity-services/dossiers.service';
|
import { DossiersService } from '@services/entity-services/dossiers.service';
|
||||||
import { FilesService } from '@services/entity-services/files.service';
|
import { FilesService } from '@services/entity-services/files.service';
|
||||||
|
import { BaseDialogComponent } from '@iqser/common-ui';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
templateUrl: './document-info-dialog.component.html',
|
templateUrl: './document-info-dialog.component.html',
|
||||||
styleUrls: ['./document-info-dialog.component.scss'],
|
styleUrls: ['./document-info-dialog.component.scss'],
|
||||||
})
|
})
|
||||||
export class DocumentInfoDialogComponent implements OnInit {
|
export class DocumentInfoDialogComponent extends BaseDialogComponent implements OnInit {
|
||||||
form: FormGroup;
|
form: FormGroup;
|
||||||
attributes: IFileAttributeConfig[];
|
attributes: IFileAttributeConfig[];
|
||||||
|
|
||||||
@ -21,27 +22,31 @@ export class DocumentInfoDialogComponent implements OnInit {
|
|||||||
private readonly _formBuilder: FormBuilder,
|
private readonly _formBuilder: FormBuilder,
|
||||||
private readonly _fileAttributesService: FileAttributesService,
|
private readonly _fileAttributesService: FileAttributesService,
|
||||||
private readonly _filesService: FilesService,
|
private readonly _filesService: FilesService,
|
||||||
public dialogRef: MatDialogRef<DocumentInfoDialogComponent>,
|
protected readonly _injector: Injector,
|
||||||
|
protected readonly _dialogRef: MatDialogRef<DocumentInfoDialogComponent>,
|
||||||
@Inject(MAT_DIALOG_DATA) readonly data: File,
|
@Inject(MAT_DIALOG_DATA) readonly data: File,
|
||||||
) {
|
) {
|
||||||
|
super(_injector, _dialogRef);
|
||||||
this._dossier = this._dossiersService.find(this.data.dossierId);
|
this._dossier = this._dossiersService.find(this.data.dossierId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
|
super.ngOnInit();
|
||||||
this.attributes = (
|
this.attributes = (
|
||||||
await this._fileAttributesService.getFileAttributesConfig(this._dossier.dossierTemplateId).toPromise()
|
await this._fileAttributesService.getFileAttributesConfig(this._dossier.dossierTemplateId).toPromise()
|
||||||
).fileAttributeConfigs.filter(attr => attr.editable);
|
).fileAttributeConfigs.filter(attr => attr.editable);
|
||||||
this.form = this._getForm();
|
this.form = this._getForm();
|
||||||
|
this.initialFormValue = this.form.getRawValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveDocumentInfo() {
|
async save() {
|
||||||
const attributeIdToValue = {
|
const attributeIdToValue = {
|
||||||
...this.data.fileAttributes?.attributeIdToValue,
|
...this.data.fileAttributes?.attributeIdToValue,
|
||||||
...this.form.getRawValue(),
|
...this.form.getRawValue(),
|
||||||
};
|
};
|
||||||
await this._fileAttributesService.setFileAttributes({ attributeIdToValue }, this.data.dossierId, this.data.fileId).toPromise();
|
await this._fileAttributesService.setFileAttributes({ attributeIdToValue }, this.data.dossierId, this.data.fileId).toPromise();
|
||||||
this._filesService.reload(this.data.dossierId, this.data.fileId);
|
this._filesService.reload(this.data.dossierId, this.data.fileId);
|
||||||
this.dialogRef.close(true);
|
this._dialogRef.close(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _getForm(): FormGroup {
|
private _getForm(): FormGroup {
|
||||||
|
|||||||
@ -66,9 +66,10 @@ export class EditDossierAttributesComponent implements EditDossierSectionInterfa
|
|||||||
async save(): EditDossierSaveResult {
|
async save(): EditDossierSaveResult {
|
||||||
const dossierAttributeList = this.attributes.map(attr => ({
|
const dossierAttributeList = this.attributes.map(attr => ({
|
||||||
dossierAttributeConfigId: attr.id,
|
dossierAttributeConfigId: attr.id,
|
||||||
value: this.isSpecificType(attr, DossierAttributeConfigTypes.DATE)
|
value:
|
||||||
? moment(this.currentAttrValue(attr)).format('YYYY-MM-DD')
|
this.isSpecificType(attr, DossierAttributeConfigTypes.DATE) && !!this.currentAttrValue(attr)
|
||||||
: this.currentAttrValue(attr),
|
? moment(this.currentAttrValue(attr)).format('YYYY-MM-DD')
|
||||||
|
: this.currentAttrValue(attr),
|
||||||
}));
|
}));
|
||||||
try {
|
try {
|
||||||
await this._dossierAttributesService.setAttributes(this.dossier, dossierAttributeList).toPromise();
|
await this._dossierAttributesService.setAttributes(this.dossier, dossierAttributeList).toPromise();
|
||||||
|
|||||||
@ -1,12 +1,22 @@
|
|||||||
<div class="header-wrapper">
|
<div class="header-wrapper">
|
||||||
<div class="heading">
|
<div class="header-left">
|
||||||
<div>{{ dossierDictionary?.label }}</div>
|
<div class="heading">
|
||||||
<div class="small-label stats-subtitle">
|
<div>{{ dossierDictionary?.label }}</div>
|
||||||
<div>
|
<div class="small-label stats-subtitle">
|
||||||
<mat-icon svgIcon="red:entries"></mat-icon>
|
<div>
|
||||||
{{ 'edit-dossier-dialog.dictionary.entries' | translate: { length: (dossierDictionary?.entries || []).length } }}
|
<mat-icon svgIcon="red:entries"></mat-icon>
|
||||||
|
{{ 'edit-dossier-dialog.dictionary.entries' | translate: { length: (dossierDictionary?.entries || []).length } }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<form [formGroup]="form" *ngIf="form">
|
||||||
|
<div class="iqser-input-group">
|
||||||
|
<mat-checkbox color="primary" formControlName="addToDictionaryAction" name="addToDictionaryAction">
|
||||||
|
{{ 'add-edit-dictionary.form.add-to-dictionary-action' | translate }}
|
||||||
|
</mat-checkbox>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="canEdit" class="display-name">
|
<div *ngIf="canEdit" class="display-name">
|
||||||
|
|||||||
@ -2,6 +2,14 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|
||||||
|
.header-left {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
.iqser-input-group {
|
||||||
|
margin-left: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.display-name {
|
.display-name {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import { DictionaryService } from '@shared/services/dictionary.service';
|
|||||||
import { CircleButtonTypes, LoadingService, Toaster } from '@iqser/common-ui';
|
import { CircleButtonTypes, LoadingService, Toaster } from '@iqser/common-ui';
|
||||||
import { DossiersService } from '@services/entity-services/dossiers.service';
|
import { DossiersService } from '@services/entity-services/dossiers.service';
|
||||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||||
|
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'redaction-edit-dossier-dictionary',
|
selector: 'redaction-edit-dossier-dictionary',
|
||||||
@ -15,10 +16,13 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
|||||||
})
|
})
|
||||||
export class EditDossierDictionaryComponent implements EditDossierSectionInterface, OnInit {
|
export class EditDossierDictionaryComponent implements EditDossierSectionInterface, OnInit {
|
||||||
@Input() dossier: Dossier;
|
@Input() dossier: Dossier;
|
||||||
|
|
||||||
|
form: FormGroup;
|
||||||
canEdit = false;
|
canEdit = false;
|
||||||
readonly circleButtonTypes = CircleButtonTypes;
|
|
||||||
dossierDictionary: IDictionary;
|
dossierDictionary: IDictionary;
|
||||||
|
|
||||||
|
readonly circleButtonTypes = CircleButtonTypes;
|
||||||
|
|
||||||
@ViewChild(DictionaryManagerComponent, { static: false }) private readonly _dictionaryManager: DictionaryManagerComponent;
|
@ViewChild(DictionaryManagerComponent, { static: false }) private readonly _dictionaryManager: DictionaryManagerComponent;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@ -26,11 +30,20 @@ export class EditDossierDictionaryComponent implements EditDossierSectionInterfa
|
|||||||
private readonly _dictionaryService: DictionaryService,
|
private readonly _dictionaryService: DictionaryService,
|
||||||
private readonly _permissionsService: PermissionsService,
|
private readonly _permissionsService: PermissionsService,
|
||||||
private readonly _loadingService: LoadingService,
|
private readonly _loadingService: LoadingService,
|
||||||
|
private readonly _formBuilder: FormBuilder,
|
||||||
private readonly _toaster: Toaster,
|
private readonly _toaster: Toaster,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
get formChanged() {
|
||||||
|
if (this.form) {
|
||||||
|
return this.form.get('addToDictionaryAction').value !== this.dossierDictionary.addToDictionaryAction;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
get changed(): boolean {
|
get changed(): boolean {
|
||||||
return this._dictionaryManager.editor.hasChanges;
|
return this._dictionaryManager.editor.hasChanges || this.formChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
get disabled(): boolean {
|
get disabled(): boolean {
|
||||||
@ -38,13 +51,14 @@ export class EditDossierDictionaryComponent implements EditDossierSectionInterfa
|
|||||||
}
|
}
|
||||||
|
|
||||||
get valid(): boolean {
|
get valid(): boolean {
|
||||||
return this._dictionaryManager.editor.hasChanges;
|
return this._dictionaryManager.editor.hasChanges || this.formChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
this._loadingService.start();
|
this._loadingService.start();
|
||||||
this.canEdit = this._permissionsService.isDossierMember(this.dossier);
|
this.canEdit = this._permissionsService.isDossierMember(this.dossier);
|
||||||
await this._updateDossierDictionary();
|
await this._updateDossierDictionary();
|
||||||
|
this.form = this._getForm();
|
||||||
this._loadingService.stop();
|
this._loadingService.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,6 +80,15 @@ export class EditDossierDictionaryComponent implements EditDossierSectionInterfa
|
|||||||
|
|
||||||
async save(): EditDossierSaveResult {
|
async save(): EditDossierSaveResult {
|
||||||
try {
|
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
|
await this._dictionaryService
|
||||||
.saveEntries(
|
.saveEntries(
|
||||||
this._dictionaryManager.editor.currentEntries,
|
this._dictionaryManager.editor.currentEntries,
|
||||||
@ -76,6 +99,7 @@ export class EditDossierDictionaryComponent implements EditDossierSectionInterfa
|
|||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
.toPromise();
|
.toPromise();
|
||||||
|
|
||||||
await this._updateDossierDictionary();
|
await this._updateDossierDictionary();
|
||||||
return { success: true };
|
return { success: true };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -85,6 +109,20 @@ export class EditDossierDictionaryComponent implements EditDossierSectionInterfa
|
|||||||
|
|
||||||
revert() {
|
revert() {
|
||||||
this._dictionaryManager.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() {
|
private async _updateDossierDictionary() {
|
||||||
|
|||||||
@ -53,7 +53,7 @@
|
|||||||
{{ 'edit-dossier-dialog.actions.save' | translate }}
|
{{ 'edit-dossier-dialog.actions.save' | translate }}
|
||||||
</button>
|
</button>
|
||||||
<iqser-icon-button
|
<iqser-icon-button
|
||||||
(action)="save(true)"
|
(action)="save({ closeAfterSave: true })"
|
||||||
[disabled]="disabled || !valid || !changed"
|
[disabled]="disabled || !valid || !changed"
|
||||||
[label]="'edit-dossier-dialog.actions.save-and-close' | translate"
|
[label]="'edit-dossier-dialog.actions.save-and-close' | translate"
|
||||||
[type]="iconButtonTypes.dark"
|
[type]="iconButtonTypes.dark"
|
||||||
@ -65,5 +65,5 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<iqser-circle-button class="dialog-close" icon="iqser:close" mat-dialog-close></iqser-circle-button>
|
<iqser-circle-button class="dialog-close" icon="iqser:close" (action)="close()"></iqser-circle-button>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@ -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 { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||||
import { Dossier } from '@red/domain';
|
import { Dossier } from '@red/domain';
|
||||||
import { EditDossierGeneralInfoComponent } from './general-info/edit-dossier-general-info.component';
|
import { EditDossierGeneralInfoComponent } from './general-info/edit-dossier-general-info.component';
|
||||||
import { EditDossierDownloadPackageComponent } from './download-package/edit-dossier-download-package.component';
|
import { EditDossierDownloadPackageComponent } from './download-package/edit-dossier-download-package.component';
|
||||||
import { EditDossierSectionInterface } from './edit-dossier-section.interface';
|
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 { EditDossierDictionaryComponent } from './dictionary/edit-dossier-dictionary.component';
|
||||||
import { EditDossierAttributesComponent } from './attributes/edit-dossier-attributes.component';
|
import { EditDossierAttributesComponent } from './attributes/edit-dossier-attributes.component';
|
||||||
|
|
||||||
@ -40,16 +40,17 @@ export class EditDossierDialogComponent extends BaseDialogComponent {
|
|||||||
private readonly _toaster: Toaster,
|
private readonly _toaster: Toaster,
|
||||||
private readonly _dossiersService: DossiersService,
|
private readonly _dossiersService: DossiersService,
|
||||||
private readonly _changeRef: ChangeDetectorRef,
|
private readonly _changeRef: ChangeDetectorRef,
|
||||||
private readonly _dialogRef: MatDialogRef<EditDossierDialogComponent>,
|
|
||||||
private readonly _loadingService: LoadingService,
|
private readonly _loadingService: LoadingService,
|
||||||
private readonly _permissionsService: PermissionsService,
|
private readonly _permissionsService: PermissionsService,
|
||||||
|
protected readonly _injector: Injector,
|
||||||
|
protected readonly _dialogRef: MatDialogRef<EditDossierDialogComponent>,
|
||||||
@Inject(MAT_DIALOG_DATA)
|
@Inject(MAT_DIALOG_DATA)
|
||||||
private readonly _data: {
|
private readonly _data: {
|
||||||
dossierId: string;
|
dossierId: string;
|
||||||
section?: Section;
|
section?: Section;
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
super();
|
super(_injector, _dialogRef);
|
||||||
this.navItems = [
|
this.navItems = [
|
||||||
{
|
{
|
||||||
key: 'dossierInfo',
|
key: 'dossierInfo',
|
||||||
@ -126,7 +127,7 @@ export class EditDossierDialogComponent extends BaseDialogComponent {
|
|||||||
return this.activeComponent?.disabled;
|
return this.activeComponent?.disabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
async save(closeAfterSave: boolean = false) {
|
async save(options?: SaveOptions) {
|
||||||
this._loadingService.start();
|
this._loadingService.start();
|
||||||
const result = await this.activeComponent.save();
|
const result = await this.activeComponent.save();
|
||||||
this._loadingService.stop();
|
this._loadingService.stop();
|
||||||
@ -135,7 +136,7 @@ export class EditDossierDialogComponent extends BaseDialogComponent {
|
|||||||
this._toaster.success(_('edit-dossier-dialog.change-successful'), { params: { dossierName: this._dossierName } });
|
this._toaster.success(_('edit-dossier-dialog.change-successful'), { params: { dossierName: this._dossierName } });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result.success && closeAfterSave) {
|
if (result.success && options?.closeAfterSave) {
|
||||||
this._dialogRef.close();
|
this._dialogRef.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -146,9 +147,19 @@ export class EditDossierDialogComponent extends BaseDialogComponent {
|
|||||||
|
|
||||||
changeTab(key: Section) {
|
changeTab(key: Section) {
|
||||||
if (this.changed) {
|
if (this.changed) {
|
||||||
this._toaster.error(_('edit-dossier-dialog.unsaved-changes'));
|
this._openConfirmDialog().then(async result => {
|
||||||
return;
|
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<section class="dialog">
|
<section class="dialog">
|
||||||
<form (submit)="handleForceAnnotation()" [formGroup]="redactionForm">
|
<form (submit)="save()" [formGroup]="form">
|
||||||
<div class="dialog-header heading-l" translate="manual-annotation.dialog.header.force-redaction" *ngIf="!isHintDialog"></div>
|
<div class="dialog-header heading-l" translate="manual-annotation.dialog.header.force-redaction" *ngIf="!isHintDialog"></div>
|
||||||
<div class="dialog-header heading-l" translate="manual-annotation.dialog.header.force-hint" *ngIf="isHintDialog"></div>
|
<div class="dialog-header heading-l" translate="manual-annotation.dialog.header.force-hint" *ngIf="isHintDialog"></div>
|
||||||
|
|
||||||
@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
<div class="iqser-input-group w-400" *ngIf="!isHintDialog">
|
<div class="iqser-input-group w-400" *ngIf="!isHintDialog">
|
||||||
<label translate="manual-annotation.dialog.content.legalBasis"></label>
|
<label translate="manual-annotation.dialog.content.legalBasis"></label>
|
||||||
<input [value]="redactionForm.get('reason').value?.legalBasis" disabled type="text" />
|
<input [value]="form.get('reason').value?.legalBasis" disabled type="text" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div [class.required]="!isDocumentAdmin" class="iqser-input-group w-300">
|
<div [class.required]="!isDocumentAdmin" class="iqser-input-group w-300">
|
||||||
@ -29,11 +29,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="dialog-actions">
|
<div class="dialog-actions">
|
||||||
<button [disabled]="!redactionForm.valid" color="primary" mat-flat-button type="submit">
|
<button [disabled]="disabled" color="primary" mat-flat-button type="submit">
|
||||||
{{ 'manual-annotation.dialog.actions.save' | translate }}
|
{{ 'manual-annotation.dialog.actions.save' | translate }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<iqser-circle-button class="dialog-close" icon="iqser:close" mat-dialog-close></iqser-circle-button>
|
<iqser-circle-button class="dialog-close" icon="iqser:close" (action)="close()"></iqser-circle-button>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@ -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 { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
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 { TranslateService } from '@ngx-translate/core';
|
||||||
import { UserService } from '@services/user.service';
|
import { UserService } from '@services/user.service';
|
||||||
import { ManualAnnotationService } from '../../services/manual-annotation.service';
|
import { ManualAnnotationService } from '../../services/manual-annotation.service';
|
||||||
@ -21,8 +21,7 @@ export interface LegalBasisOption {
|
|||||||
templateUrl: './force-annotation-dialog.component.html',
|
templateUrl: './force-annotation-dialog.component.html',
|
||||||
styleUrls: ['./force-annotation-dialog.component.scss'],
|
styleUrls: ['./force-annotation-dialog.component.scss'],
|
||||||
})
|
})
|
||||||
export class ForceAnnotationDialogComponent implements OnInit {
|
export class ForceAnnotationDialogComponent extends BaseDialogComponent implements OnInit {
|
||||||
redactionForm: FormGroup;
|
|
||||||
isDocumentAdmin: boolean;
|
isDocumentAdmin: boolean;
|
||||||
legalOptions: LegalBasisOption[] = [];
|
legalOptions: LegalBasisOption[] = [];
|
||||||
|
|
||||||
@ -35,10 +34,13 @@ export class ForceAnnotationDialogComponent implements OnInit {
|
|||||||
private readonly _justificationsService: JustificationsService,
|
private readonly _justificationsService: JustificationsService,
|
||||||
private readonly _manualAnnotationService: ManualAnnotationService,
|
private readonly _manualAnnotationService: ManualAnnotationService,
|
||||||
private readonly _permissionsService: PermissionsService,
|
private readonly _permissionsService: PermissionsService,
|
||||||
public dialogRef: MatDialogRef<ForceAnnotationDialogComponent>,
|
protected readonly _injector: Injector,
|
||||||
|
protected readonly _dialogRef: MatDialogRef<ForceAnnotationDialogComponent>,
|
||||||
@Inject(MAT_DIALOG_DATA) private readonly _data: { readonly dossier: Dossier; readonly hint: boolean },
|
@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() {
|
get isHintDialog() {
|
||||||
@ -55,6 +57,7 @@ export class ForceAnnotationDialogComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
|
super.ngOnInit();
|
||||||
const data = await this._justificationsService.getForDossierTemplate(this._data.dossier.dossierTemplateId).toPromise();
|
const data = await this._justificationsService.getForDossierTemplate(this._data.dossier.dossierTemplateId).toPromise();
|
||||||
|
|
||||||
this.legalOptions = data.map(lbm => ({
|
this.legalOptions = data.map(lbm => ({
|
||||||
@ -66,17 +69,17 @@ export class ForceAnnotationDialogComponent implements OnInit {
|
|||||||
this.legalOptions.sort((a, b) => a.label.localeCompare(b.label));
|
this.legalOptions.sort((a, b) => a.label.localeCompare(b.label));
|
||||||
}
|
}
|
||||||
|
|
||||||
handleForceAnnotation() {
|
save() {
|
||||||
this.dialogRef.close(this._createForceRedactionRequest());
|
this._dialogRef.close(this._createForceRedactionRequest());
|
||||||
}
|
}
|
||||||
|
|
||||||
private _createForceRedactionRequest(): ILegalBasisChangeRequest {
|
private _createForceRedactionRequest(): ILegalBasisChangeRequest {
|
||||||
const request: 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.legalBasis = legalOption.legalBasis;
|
||||||
request.comment = this.redactionForm.get('comment').value;
|
request.comment = this.form.get('comment').value;
|
||||||
|
|
||||||
return request;
|
return request;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<section class="dialog">
|
<section class="dialog">
|
||||||
<form (submit)="handleAddRedaction()" [formGroup]="redactionForm">
|
<form (submit)="save()" [formGroup]="form">
|
||||||
<div [translate]="title" class="dialog-header heading-l"></div>
|
<div [translate]="title" class="dialog-header heading-l"></div>
|
||||||
|
|
||||||
<div class="dialog-content">
|
<div class="dialog-content">
|
||||||
@ -54,7 +54,7 @@
|
|||||||
|
|
||||||
<div *ngIf="!isDictionaryRequest" class="iqser-input-group w-400">
|
<div *ngIf="!isDictionaryRequest" class="iqser-input-group w-400">
|
||||||
<label translate="manual-annotation.dialog.content.legalBasis"></label>
|
<label translate="manual-annotation.dialog.content.legalBasis"></label>
|
||||||
<input [value]="redactionForm.get('reason').value?.legalBasis" disabled type="text" />
|
<input [value]="form.get('reason').value?.legalBasis" disabled type="text" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="data.manualRedactionEntryWrapper.manualRedactionEntry.rectangle" class="iqser-input-group w-400">
|
<div *ngIf="data.manualRedactionEntryWrapper.manualRedactionEntry.rectangle" class="iqser-input-group w-400">
|
||||||
@ -74,11 +74,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="dialog-actions">
|
<div class="dialog-actions">
|
||||||
<button [disabled]="!redactionForm.valid" color="primary" mat-flat-button type="submit">
|
<button [disabled]="disabled" color="primary" mat-flat-button type="submit">
|
||||||
{{ 'manual-annotation.dialog.actions.save' | translate }}
|
{{ 'manual-annotation.dialog.actions.save' | translate }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<iqser-circle-button class="dialog-close" icon="iqser:close" mat-dialog-close></iqser-circle-button>
|
<iqser-circle-button class="dialog-close" icon="iqser:close" (action)="close()"></iqser-circle-button>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@ -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 { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||||
import { AppStateService } from '@state/app-state.service';
|
import { AppStateService } from '@state/app-state.service';
|
||||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
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 { JustificationsService } from '@services/entity-services/justifications.service';
|
||||||
import { Dictionary, Dossier, File, IAddRedactionRequest } from '@red/domain';
|
import { Dictionary, Dossier, File, IAddRedactionRequest } from '@red/domain';
|
||||||
import { DossiersService } from '@services/entity-services/dossiers.service';
|
import { DossiersService } from '@services/entity-services/dossiers.service';
|
||||||
|
import { BaseDialogComponent } from '@iqser/common-ui';
|
||||||
|
import { DictionaryService } from '@shared/services/dictionary.service';
|
||||||
|
|
||||||
export interface LegalBasisOption {
|
export interface LegalBasisOption {
|
||||||
label?: string;
|
label?: string;
|
||||||
@ -21,9 +23,7 @@ export interface LegalBasisOption {
|
|||||||
templateUrl: './manual-annotation-dialog.component.html',
|
templateUrl: './manual-annotation-dialog.component.html',
|
||||||
styleUrls: ['./manual-annotation-dialog.component.scss'],
|
styleUrls: ['./manual-annotation-dialog.component.scss'],
|
||||||
})
|
})
|
||||||
export class ManualAnnotationDialogComponent implements OnInit {
|
export class ManualAnnotationDialogComponent extends BaseDialogComponent implements OnInit {
|
||||||
redactionForm: FormGroup;
|
|
||||||
|
|
||||||
isDocumentAdmin: boolean;
|
isDocumentAdmin: boolean;
|
||||||
isDictionaryRequest: boolean;
|
isDictionaryRequest: boolean;
|
||||||
isFalsePositiveRequest: boolean;
|
isFalsePositiveRequest: boolean;
|
||||||
@ -40,18 +40,21 @@ export class ManualAnnotationDialogComponent implements OnInit {
|
|||||||
private readonly _manualAnnotationService: ManualAnnotationService,
|
private readonly _manualAnnotationService: ManualAnnotationService,
|
||||||
private readonly _permissionsService: PermissionsService,
|
private readonly _permissionsService: PermissionsService,
|
||||||
private readonly _dossiersService: DossiersService,
|
private readonly _dossiersService: DossiersService,
|
||||||
|
private readonly _dictionaryService: DictionaryService,
|
||||||
public dialogRef: MatDialogRef<ManualAnnotationDialogComponent>,
|
public dialogRef: MatDialogRef<ManualAnnotationDialogComponent>,
|
||||||
|
protected readonly _injector: Injector,
|
||||||
|
protected readonly _dialogRef: MatDialogRef<ManualAnnotationDialogComponent>,
|
||||||
@Inject(MAT_DIALOG_DATA) public data: { manualRedactionEntryWrapper: ManualRedactionEntryWrapper; file: File },
|
@Inject(MAT_DIALOG_DATA) public data: { manualRedactionEntryWrapper: ManualRedactionEntryWrapper; file: File },
|
||||||
) {
|
) {
|
||||||
|
super(_injector, _dialogRef);
|
||||||
this._dossier = this._dossiersService.find(this.data.file.dossierId);
|
this._dossier = this._dossiersService.find(this.data.file.dossierId);
|
||||||
this.isDocumentAdmin = this._permissionsService.isApprover(this._dossier);
|
this.isDocumentAdmin = this._permissionsService.isApprover(this._dossier);
|
||||||
|
|
||||||
this.isFalsePositiveRequest = this.data.manualRedactionEntryWrapper.type === 'FALSE_POSITIVE';
|
this.isFalsePositiveRequest = this.data.manualRedactionEntryWrapper.type === 'FALSE_POSITIVE';
|
||||||
this.isDictionaryRequest = this.data.manualRedactionEntryWrapper.type === 'DICTIONARY' || this.isFalsePositiveRequest;
|
this.isDictionaryRequest = this.data.manualRedactionEntryWrapper.type === 'DICTIONARY' || this.isFalsePositiveRequest;
|
||||||
|
|
||||||
this.redactionForm = this._getForm();
|
this.form = this._getForm();
|
||||||
|
this.initialFormValue = this.form.getRawValue();
|
||||||
this.possibleDictionaries = this._possibleDictionaries;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get title() {
|
get title() {
|
||||||
@ -59,17 +62,21 @@ export class ManualAnnotationDialogComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get displayedDictionaryLabel() {
|
get displayedDictionaryLabel() {
|
||||||
const dictType = this.redactionForm.get('dictionary').value;
|
const dictType = this.form.get('dictionary').value;
|
||||||
if (dictType) {
|
if (dictType) {
|
||||||
return this.possibleDictionaries.find(d => d.type === dictType).label;
|
return this.possibleDictionaries.find(d => d.type === dictType).label;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private get _possibleDictionaries(): Dictionary[] {
|
private async _getPossibleDictionaries(): Promise<Dictionary[]> {
|
||||||
const possibleDictionaries: Dictionary[] = [];
|
const possibleDictionaries: Dictionary[] = [];
|
||||||
const dossier = this._dossier;
|
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])) {
|
for (const key of Object.keys(this._appStateService.dictionaryData[dossier.dossierTemplateId])) {
|
||||||
const dictionaryData = this._appStateService.getDictionary(key, dossier.dossierTemplateId);
|
const dictionaryData = this._appStateService.getDictionary(key, dossier.dossierTemplateId);
|
||||||
if (!dictionaryData.virtual && dictionaryData.addToDictionaryAction) {
|
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));
|
possibleDictionaries.sort((a, b) => a.label.localeCompare(b.label));
|
||||||
|
|
||||||
return possibleDictionaries;
|
return possibleDictionaries;
|
||||||
}
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
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 => ({
|
this.legalOptions = data.map(lbm => ({
|
||||||
legalBasis: lbm.reason,
|
legalBasis: lbm.reason,
|
||||||
description: lbm.description,
|
description: lbm.description,
|
||||||
@ -94,11 +108,11 @@ export class ManualAnnotationDialogComponent implements OnInit {
|
|||||||
this.legalOptions.sort((a, b) => a.label.localeCompare(b.label));
|
this.legalOptions.sort((a, b) => a.label.localeCompare(b.label));
|
||||||
}
|
}
|
||||||
|
|
||||||
handleAddRedaction() {
|
save() {
|
||||||
this._enhanceManualRedaction(this.data.manualRedactionEntryWrapper.manualRedactionEntry);
|
this._enhanceManualRedaction(this.data.manualRedactionEntryWrapper.manualRedactionEntry);
|
||||||
this._manualAnnotationService.addAnnotation(this.data.manualRedactionEntryWrapper.manualRedactionEntry, this.data.file).subscribe(
|
this._manualAnnotationService.addAnnotation(this.data.manualRedactionEntryWrapper.manualRedactionEntry, this.data.file).subscribe(
|
||||||
response => this.dialogRef.close(new ManualAnnotationResponse(this.data.manualRedactionEntryWrapper, response)),
|
response => this._dialogRef.close(new ManualAnnotationResponse(this.data.manualRedactionEntryWrapper, response)),
|
||||||
() => this.dialogRef.close(),
|
() => this._dialogRef.close(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,22 +137,26 @@ export class ManualAnnotationDialogComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _enhanceManualRedaction(addRedactionRequest: IAddRedactionRequest) {
|
private _enhanceManualRedaction(addRedactionRequest: IAddRedactionRequest) {
|
||||||
const legalOption: LegalBasisOption = this.redactionForm.get('reason').value;
|
const legalOption: LegalBasisOption = this.form.get('reason').value;
|
||||||
addRedactionRequest.type = this.redactionForm.get('dictionary').value;
|
addRedactionRequest.type = this.form.get('dictionary').value;
|
||||||
if (legalOption) {
|
if (legalOption) {
|
||||||
addRedactionRequest.reason = legalOption.description;
|
addRedactionRequest.reason = legalOption.description;
|
||||||
addRedactionRequest.legalBasis = legalOption.legalBasis;
|
addRedactionRequest.legalBasis = legalOption.legalBasis;
|
||||||
}
|
}
|
||||||
addRedactionRequest.addToDictionary = this.isDictionaryRequest;
|
|
||||||
// todo fix this in backend
|
// todo fix this in backend
|
||||||
|
addRedactionRequest.addToDictionary = this.isDictionaryRequest && addRedactionRequest.type !== 'dossier_redaction';
|
||||||
|
addRedactionRequest.addToDossierDictionary = this.isDictionaryRequest && addRedactionRequest.type === 'dossier_redaction';
|
||||||
|
|
||||||
if (!addRedactionRequest.reason) {
|
if (!addRedactionRequest.reason) {
|
||||||
addRedactionRequest.reason = 'Dictionary Request';
|
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.comment = commentValue ? { text: commentValue } : null;
|
||||||
addRedactionRequest.section = this.redactionForm.get('section').value;
|
addRedactionRequest.section = this.form.get('section').value;
|
||||||
addRedactionRequest.value = addRedactionRequest.rectangle
|
addRedactionRequest.value = addRedactionRequest.rectangle ? this.form.get('classification').value : addRedactionRequest.value;
|
||||||
? this.redactionForm.get('classification').value
|
}
|
||||||
: addRedactionRequest.value;
|
|
||||||
|
get disabled() {
|
||||||
|
return this.form.invalid;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<section class="dialog">
|
<section class="dialog">
|
||||||
<form (submit)="save()" [formGroup]="recategorizeImageForm">
|
<form (submit)="save()" [formGroup]="form">
|
||||||
<div class="dialog-header heading-l" translate="recategorize-image-dialog.header"></div>
|
<div class="dialog-header heading-l" translate="recategorize-image-dialog.header"></div>
|
||||||
|
|
||||||
<div class="dialog-content">
|
<div class="dialog-content">
|
||||||
@ -23,12 +23,12 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="dialog-actions">
|
<div class="dialog-actions">
|
||||||
<button [disabled]="!recategorizeImageForm.valid || !changed" color="primary" mat-flat-button type="submit">
|
<button [disabled]="disabled" color="primary" mat-flat-button type="submit">
|
||||||
{{ 'recategorize-image-dialog.actions.save' | translate }}
|
{{ 'recategorize-image-dialog.actions.save' | translate }}
|
||||||
</button>
|
</button>
|
||||||
<div class="all-caps-label cancel" mat-dialog-close translate="recategorize-image-dialog.actions.cancel"></div>
|
<div class="all-caps-label cancel" mat-dialog-close translate="recategorize-image-dialog.actions.cancel"></div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<iqser-circle-button class="dialog-close" icon="iqser:close" mat-dialog-close></iqser-circle-button>
|
<iqser-circle-button class="dialog-close" icon="iqser:close" (action)="close()"></iqser-circle-button>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@ -1,17 +1,17 @@
|
|||||||
import { Component, Inject, OnInit } from '@angular/core';
|
import { Component, Inject, Injector, OnInit } from '@angular/core';
|
||||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
import { FormBuilder, Validators } from '@angular/forms';
|
||||||
import { PermissionsService } from '@services/permissions.service';
|
import { PermissionsService } from '@services/permissions.service';
|
||||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||||
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
||||||
import { imageCategoriesTranslations } from '../../translations/image-categories-translations';
|
import { imageCategoriesTranslations } from '../../translations/image-categories-translations';
|
||||||
import { ImageCategory } from '../../models/image-category.model';
|
import { ImageCategory } from '../../models/image-category.model';
|
||||||
import { Dossier } from '@red/domain';
|
import { Dossier } from '@red/domain';
|
||||||
|
import { BaseDialogComponent } from '@iqser/common-ui';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
templateUrl: './recategorize-image-dialog.component.html',
|
templateUrl: './recategorize-image-dialog.component.html',
|
||||||
})
|
})
|
||||||
export class RecategorizeImageDialogComponent implements OnInit {
|
export class RecategorizeImageDialogComponent extends BaseDialogComponent implements OnInit {
|
||||||
recategorizeImageForm: FormGroup;
|
|
||||||
isDocumentAdmin: boolean;
|
isDocumentAdmin: boolean;
|
||||||
typeOptions: ImageCategory[] = ['signature', 'logo', 'formula', 'image'];
|
typeOptions: ImageCategory[] = ['signature', 'logo', 'formula', 'image'];
|
||||||
translations = imageCategoriesTranslations;
|
translations = imageCategoriesTranslations;
|
||||||
@ -19,27 +19,32 @@ export class RecategorizeImageDialogComponent implements OnInit {
|
|||||||
constructor(
|
constructor(
|
||||||
private readonly _permissionsService: PermissionsService,
|
private readonly _permissionsService: PermissionsService,
|
||||||
private readonly _formBuilder: FormBuilder,
|
private readonly _formBuilder: FormBuilder,
|
||||||
public dialogRef: MatDialogRef<RecategorizeImageDialogComponent>,
|
protected readonly _injector: Injector,
|
||||||
|
protected readonly _dialogRef: MatDialogRef<RecategorizeImageDialogComponent>,
|
||||||
@Inject(MAT_DIALOG_DATA) public data: { annotations: AnnotationWrapper[]; dossier: Dossier },
|
@Inject(MAT_DIALOG_DATA) public data: { annotations: AnnotationWrapper[]; dossier: Dossier },
|
||||||
) {}
|
) {
|
||||||
|
super(_injector, _dialogRef);
|
||||||
|
}
|
||||||
|
|
||||||
get changed(): boolean {
|
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() {
|
ngOnInit() {
|
||||||
|
super.ngOnInit();
|
||||||
this.isDocumentAdmin = this._permissionsService.isApprover(this.data.dossier);
|
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],
|
type: [this.data.annotations[0].type, Validators.required],
|
||||||
comment: this.isDocumentAdmin ? [null] : [null, Validators.required],
|
comment: this.isDocumentAdmin ? [null] : [null, Validators.required],
|
||||||
});
|
});
|
||||||
|
this.initialFormValue = this.form.getRawValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
save() {
|
save() {
|
||||||
this.dialogRef.close({
|
this._dialogRef.close({
|
||||||
type: this.recategorizeImageForm.get('type').value,
|
type: this.form.get('type').value,
|
||||||
comment: this.recategorizeImageForm.get('comment').value,
|
comment: this.form.get('comment').value,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,7 +7,7 @@
|
|||||||
) | translate: { hint: data.hint }
|
) | translate: { hint: data.hint }
|
||||||
}}
|
}}
|
||||||
</div>
|
</div>
|
||||||
<form (submit)="confirm()" [formGroup]="redactionForm">
|
<form (submit)="save()" [formGroup]="form">
|
||||||
<div class="dialog-content">
|
<div class="dialog-content">
|
||||||
{{
|
{{
|
||||||
(data.removeFromDictionary
|
(data.removeFromDictionary
|
||||||
@ -46,12 +46,12 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="dialog-actions">
|
<div class="dialog-actions">
|
||||||
<button [disabled]="!redactionForm.valid" color="primary" mat-flat-button type="submit">
|
<button [disabled]="disabled" color="primary" mat-flat-button type="submit">
|
||||||
{{ 'remove-annotations-dialog.confirm' | translate }}
|
{{ 'remove-annotations-dialog.confirm' | translate }}
|
||||||
</button>
|
</button>
|
||||||
<div class="all-caps-label cancel" mat-dialog-close translate="remove-annotations-dialog.cancel"></div>
|
<div class="all-caps-label cancel" mat-dialog-close translate="remove-annotations-dialog.cancel"></div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<iqser-circle-button class="dialog-close" icon="iqser:close" mat-dialog-close></iqser-circle-button>
|
<iqser-circle-button class="dialog-close" icon="iqser:close" (action)="close()"></iqser-circle-button>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@ -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 { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||||
import { PermissionsService } from '@services/permissions.service';
|
import { PermissionsService } from '@services/permissions.service';
|
||||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
import { FormBuilder, Validators } from '@angular/forms';
|
||||||
import { humanize } from '@iqser/common-ui';
|
import { BaseDialogComponent, humanize } from '@iqser/common-ui';
|
||||||
import { Dossier } from '@red/domain';
|
import { Dossier } from '@red/domain';
|
||||||
|
|
||||||
export interface RemoveAnnotationsDialogInput {
|
export interface RemoveAnnotationsDialogInput {
|
||||||
@ -18,23 +18,24 @@ export interface RemoveAnnotationsDialogInput {
|
|||||||
templateUrl: './remove-annotations-dialog.component.html',
|
templateUrl: './remove-annotations-dialog.component.html',
|
||||||
styleUrls: ['./remove-annotations-dialog.component.scss'],
|
styleUrls: ['./remove-annotations-dialog.component.scss'],
|
||||||
})
|
})
|
||||||
export class RemoveAnnotationsDialogComponent {
|
export class RemoveAnnotationsDialogComponent extends BaseDialogComponent {
|
||||||
redactionForm: FormGroup;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly _translateService: TranslateService,
|
private readonly _translateService: TranslateService,
|
||||||
private readonly _formBuilder: FormBuilder,
|
private readonly _formBuilder: FormBuilder,
|
||||||
readonly permissionsService: PermissionsService,
|
readonly permissionsService: PermissionsService,
|
||||||
public dialogRef: MatDialogRef<RemoveAnnotationsDialogComponent>,
|
protected readonly _injector: Injector,
|
||||||
|
protected readonly _dialogRef: MatDialogRef<RemoveAnnotationsDialogComponent>,
|
||||||
@Inject(MAT_DIALOG_DATA) public data: RemoveAnnotationsDialogInput,
|
@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],
|
comment: this.permissionsService.isApprover(this.data.dossier) ? [null] : [null, Validators.required],
|
||||||
});
|
});
|
||||||
|
this.initialFormValue = this.form.getRawValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
confirm() {
|
save() {
|
||||||
this.dialogRef.close({ comment: this.redactionForm.getRawValue().comment });
|
this._dialogRef.close({ comment: this.form.getRawValue().comment });
|
||||||
}
|
}
|
||||||
|
|
||||||
printable(annotation: AnnotationWrapper) {
|
printable(annotation: AnnotationWrapper) {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<section class="dialog">
|
<section class="dialog">
|
||||||
<form (submit)="save()" [formGroup]="resizeForm">
|
<form (submit)="save()" [formGroup]="form">
|
||||||
<div class="dialog-header heading-l" translate="resize-annotation-dialog.header"></div>
|
<div class="dialog-header heading-l" translate="resize-annotation-dialog.header"></div>
|
||||||
|
|
||||||
<div class="dialog-content">
|
<div class="dialog-content">
|
||||||
@ -10,12 +10,12 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="dialog-actions">
|
<div class="dialog-actions">
|
||||||
<button [disabled]="!resizeForm.valid" color="primary" mat-flat-button type="submit">
|
<button [disabled]="disabled" color="primary" mat-flat-button type="submit">
|
||||||
{{ 'resize-annotation-dialog.actions.save' | translate }}
|
{{ 'resize-annotation-dialog.actions.save' | translate }}
|
||||||
</button>
|
</button>
|
||||||
<div class="all-caps-label cancel" mat-dialog-close translate="resize-annotation-dialog.actions.cancel"></div>
|
<div class="all-caps-label cancel" mat-dialog-close translate="resize-annotation-dialog.actions.cancel"></div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<iqser-circle-button class="dialog-close" icon="iqser:close" mat-dialog-close></iqser-circle-button>
|
<iqser-circle-button class="dialog-close" icon="iqser:close" (action)="close()"></iqser-circle-button>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@ -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 { 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 { PermissionsService } from '@services/permissions.service';
|
||||||
import { Dossier } from '@red/domain';
|
import { Dossier } from '@red/domain';
|
||||||
|
import { BaseDialogComponent } from '@iqser/common-ui';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
templateUrl: './resize-annotation-dialog.component.html',
|
templateUrl: './resize-annotation-dialog.component.html',
|
||||||
})
|
})
|
||||||
export class ResizeAnnotationDialogComponent implements OnInit {
|
export class ResizeAnnotationDialogComponent extends BaseDialogComponent implements OnInit {
|
||||||
resizeForm: FormGroup;
|
|
||||||
isDocumentAdmin: boolean;
|
isDocumentAdmin: boolean;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly _permissionsService: PermissionsService,
|
private readonly _permissionsService: PermissionsService,
|
||||||
private readonly _formBuilder: FormBuilder,
|
private readonly _formBuilder: FormBuilder,
|
||||||
public dialogRef: MatDialogRef<ResizeAnnotationDialogComponent>,
|
protected readonly _injector: Injector,
|
||||||
|
protected readonly _dialogRef: MatDialogRef<ResizeAnnotationDialogComponent>,
|
||||||
@Inject(MAT_DIALOG_DATA) private readonly _data: { dossier: Dossier },
|
@Inject(MAT_DIALOG_DATA) private readonly _data: { dossier: Dossier },
|
||||||
) {}
|
) {
|
||||||
|
super(_injector, _dialogRef);
|
||||||
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
super.ngOnInit();
|
||||||
|
|
||||||
this.isDocumentAdmin = this._permissionsService.isApprover(this._data.dossier);
|
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],
|
comment: this.isDocumentAdmin ? [null] : [null, Validators.required],
|
||||||
});
|
});
|
||||||
|
this.initialFormValue = this.form.getRawValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
save() {
|
save() {
|
||||||
this.dialogRef.close({
|
this._dialogRef.close({
|
||||||
comment: this.resizeForm.get('comment').value,
|
comment: this.form.get('comment').value,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,10 +10,7 @@ import { SharedModule } from '@shared/shared.module';
|
|||||||
import { DossiersRoutingModule } from './dossiers-routing.module';
|
import { DossiersRoutingModule } from './dossiers-routing.module';
|
||||||
import { FileUploadDownloadModule } from '@upload-download/file-upload-download.module';
|
import { FileUploadDownloadModule } from '@upload-download/file-upload-download.module';
|
||||||
import { DossiersDialogService } from './services/dossiers-dialog.service';
|
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 { ManualAnnotationService } from './services/manual-annotation.service';
|
||||||
import { AnnotationDrawService } from './services/annotation-draw.service';
|
|
||||||
import { AnnotationProcessingService } from './services/annotation-processing.service';
|
import { AnnotationProcessingService } from './services/annotation-processing.service';
|
||||||
import { EditDossierDialogComponent } from './dialogs/edit-dossier-dialog/edit-dossier-dialog.component';
|
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';
|
import { EditDossierGeneralInfoComponent } from './dialogs/edit-dossier-dialog/general-info/edit-dossier-general-info.component';
|
||||||
@ -56,14 +53,7 @@ const components = [
|
|||||||
...dialogs,
|
...dialogs,
|
||||||
];
|
];
|
||||||
|
|
||||||
const services = [
|
const services = [DossiersDialogService, ManualAnnotationService, AnnotationProcessingService];
|
||||||
DossiersDialogService,
|
|
||||||
AnnotationActionsService,
|
|
||||||
ManualAnnotationService,
|
|
||||||
PdfViewerDataService,
|
|
||||||
AnnotationDrawService,
|
|
||||||
AnnotationProcessingService,
|
|
||||||
];
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [...components],
|
declarations: [...components],
|
||||||
|
|||||||
@ -5,10 +5,15 @@
|
|||||||
[showCloseButton]="true"
|
[showCloseButton]="true"
|
||||||
[viewModeSelection]="viewModeSelection"
|
[viewModeSelection]="viewModeSelection"
|
||||||
>
|
>
|
||||||
<redaction-file-download-btn [files]="entitiesService.all$ | async" tooltipPosition="below"></redaction-file-download-btn>
|
<redaction-file-download-btn
|
||||||
|
[disabled]="listingService.areSomeSelected$ | async"
|
||||||
|
[files]="entitiesService.all$ | async"
|
||||||
|
tooltipPosition="below"
|
||||||
|
></redaction-file-download-btn>
|
||||||
|
|
||||||
<iqser-circle-button
|
<iqser-circle-button
|
||||||
(action)="exportFilesAsCSV()"
|
(action)="exportFilesAsCSV()"
|
||||||
|
[disabled]="listingService.areSomeSelected$ | async"
|
||||||
[tooltip]="'dossier-overview.header-actions.download-csv' | translate"
|
[tooltip]="'dossier-overview.header-actions.download-csv' | translate"
|
||||||
icon="iqser:csv"
|
icon="iqser:csv"
|
||||||
tooltipPosition="below"
|
tooltipPosition="below"
|
||||||
@ -17,7 +22,8 @@
|
|||||||
<iqser-circle-button
|
<iqser-circle-button
|
||||||
(action)="reanalyseDossier()"
|
(action)="reanalyseDossier()"
|
||||||
*ngIf="permissionsService.displayReanalyseBtn(dossier) && analysisForced"
|
*ngIf="permissionsService.displayReanalyseBtn(dossier) && analysisForced"
|
||||||
[tooltipClass]="'small ' + ((listingService.areSomeSelected$ | async) ? '' : 'warn')"
|
[disabled]="listingService.areSomeSelected$ | async"
|
||||||
|
[tooltipClass]="'small warn'"
|
||||||
[tooltip]="'dossier-overview.new-rule.toast.actions.reanalyse-all' | translate"
|
[tooltip]="'dossier-overview.new-rule.toast.actions.reanalyse-all' | translate"
|
||||||
[type]="circleButtonTypes.warn"
|
[type]="circleButtonTypes.warn"
|
||||||
icon="iqser:refresh"
|
icon="iqser:refresh"
|
||||||
|
|||||||
@ -47,7 +47,7 @@ export class ScreenHeaderComponent implements OnInit {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.actionConfigs = this.configService.actionConfig(this.dossier.dossierId);
|
this.actionConfigs = this.configService.actionConfig(this.dossier.dossierId, this.listingService.areSomeSelected$);
|
||||||
}
|
}
|
||||||
|
|
||||||
async reanalyseDossier() {
|
async reanalyseDossier() {
|
||||||
|
|||||||
@ -1,6 +1,11 @@
|
|||||||
<div class="needs-work">
|
<div class="needs-work">
|
||||||
<redaction-annotation-icon *ngIf="file.analysisRequired" [color]="analysisColor" label="A" type="square"></redaction-annotation-icon>
|
<redaction-annotation-icon *ngIf="file.analysisRequired" [color]="analysisColor" label="A" type="square"></redaction-annotation-icon>
|
||||||
<redaction-annotation-icon *ngIf="file.hasUpdates" [color]="updatedColor" label="U" type="square"></redaction-annotation-icon>
|
<redaction-annotation-icon
|
||||||
|
*ngIf="file.hasUpdates && file.assignee === userService.currentUser.id"
|
||||||
|
[color]="updatedColor"
|
||||||
|
label="U"
|
||||||
|
type="square"
|
||||||
|
></redaction-annotation-icon>
|
||||||
<redaction-annotation-icon *ngIf="file.hasRedactions" [color]="redactionColor" label="R" type="square"></redaction-annotation-icon>
|
<redaction-annotation-icon *ngIf="file.hasRedactions" [color]="redactionColor" label="R" type="square"></redaction-annotation-icon>
|
||||||
<redaction-annotation-icon *ngIf="file.hasImages" [color]="imageColor" label="I" type="square"></redaction-annotation-icon>
|
<redaction-annotation-icon *ngIf="file.hasImages" [color]="imageColor" label="I" type="square"></redaction-annotation-icon>
|
||||||
<redaction-annotation-icon *ngIf="file.hintsOnly" [color]="hintColor" label="H" type="circle"></redaction-annotation-icon>
|
<redaction-annotation-icon *ngIf="file.hintsOnly" [color]="hintColor" label="H" type="circle"></redaction-annotation-icon>
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
|
|||||||
import { AppStateService } from '@state/app-state.service';
|
import { AppStateService } from '@state/app-state.service';
|
||||||
import { File } from '@red/domain';
|
import { File } from '@red/domain';
|
||||||
import { DossiersService } from '@services/entity-services/dossiers.service';
|
import { DossiersService } from '@services/entity-services/dossiers.service';
|
||||||
|
import { UserService } from '../../../../../../../services/user.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'redaction-file-workload',
|
selector: 'redaction-file-workload',
|
||||||
@ -12,7 +13,11 @@ import { DossiersService } from '@services/entity-services/dossiers.service';
|
|||||||
export class FileWorkloadComponent {
|
export class FileWorkloadComponent {
|
||||||
@Input() file: File;
|
@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() {
|
get suggestionColor() {
|
||||||
return this._getDictionaryColor('suggestion');
|
return this._getDictionaryColor('suggestion');
|
||||||
|
|||||||
@ -102,7 +102,7 @@ export class ConfigService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
actionConfig(dossierId: string): List<ActionConfig> {
|
actionConfig(dossierId: string, disabled$: Observable<boolean>): List<ActionConfig> {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
label: this._translateService.instant('dossier-overview.header-actions.edit'),
|
label: this._translateService.instant('dossier-overview.header-actions.edit'),
|
||||||
@ -110,6 +110,7 @@ export class ConfigService {
|
|||||||
icon: 'iqser:edit',
|
icon: 'iqser:edit',
|
||||||
hide: !this._userService.currentUser.isManager,
|
hide: !this._userService.currentUser.isManager,
|
||||||
helpModeKey: 'edit-dossier-attributes',
|
helpModeKey: 'edit-dossier-attributes',
|
||||||
|
disabled$,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,19 +15,19 @@ import { FileDropOverlayService } from '@upload-download/services/file-drop-over
|
|||||||
import { FileUploadModel } from '@upload-download/model/file-upload.model';
|
import { FileUploadModel } from '@upload-download/model/file-upload.model';
|
||||||
import { FileUploadService } from '@upload-download/services/file-upload.service';
|
import { FileUploadService } from '@upload-download/services/file-upload.service';
|
||||||
import { StatusOverlayService } from '@upload-download/services/status-overlay.service';
|
import { StatusOverlayService } from '@upload-download/services/status-overlay.service';
|
||||||
import * as moment from 'moment';
|
import { Observable } from 'rxjs';
|
||||||
import { Observable, timer } from 'rxjs';
|
|
||||||
import { filter, skip, switchMap, tap } from 'rxjs/operators';
|
import { filter, skip, switchMap, tap } from 'rxjs/operators';
|
||||||
import { convertFiles, Files, handleFileDrop } from '@utils/index';
|
import { convertFiles, Files, handleFileDrop } from '@utils/index';
|
||||||
import {
|
import {
|
||||||
CircleButtonTypes,
|
CircleButtonTypes,
|
||||||
|
CustomError,
|
||||||
DefaultListingServices,
|
DefaultListingServices,
|
||||||
|
ErrorService,
|
||||||
ListingComponent,
|
ListingComponent,
|
||||||
ListingModes,
|
ListingModes,
|
||||||
LoadingService,
|
LoadingService,
|
||||||
NestedFilter,
|
NestedFilter,
|
||||||
OnAttach,
|
OnAttach,
|
||||||
OnDetach,
|
|
||||||
TableColumnConfig,
|
TableColumnConfig,
|
||||||
TableComponent,
|
TableComponent,
|
||||||
WorkflowConfig,
|
WorkflowConfig,
|
||||||
@ -37,7 +37,6 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
|||||||
import { PermissionsService } from '@services/permissions.service';
|
import { PermissionsService } from '@services/permissions.service';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { FileAttributesService } from '@services/entity-services/file-attributes.service';
|
import { FileAttributesService } from '@services/entity-services/file-attributes.service';
|
||||||
import { ConfigService as AppConfigService } from '@services/config.service';
|
|
||||||
import { ConfigService } from '../config.service';
|
import { ConfigService } from '../config.service';
|
||||||
import { DossiersService } from '@services/entity-services/dossiers.service';
|
import { DossiersService } from '@services/entity-services/dossiers.service';
|
||||||
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.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 { UserPreferenceService } from '@services/user-preference.service';
|
||||||
import { FilesMapService } from '@services/entity-services/files-map.service';
|
import { FilesMapService } from '@services/entity-services/files-map.service';
|
||||||
import { FilesService } from '@services/entity-services/files.service';
|
import { FilesService } from '@services/entity-services/files.service';
|
||||||
import { CHANGED_CHECK_INTERVAL } from '@utils/constants';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
templateUrl: './dossier-overview-screen.component.html',
|
templateUrl: './dossier-overview-screen.component.html',
|
||||||
@ -53,7 +51,7 @@ import { CHANGED_CHECK_INTERVAL } from '@utils/constants';
|
|||||||
providers: [...DefaultListingServices, { provide: ListingComponent, useExisting: forwardRef(() => DossierOverviewScreenComponent) }],
|
providers: [...DefaultListingServices, { provide: ListingComponent, useExisting: forwardRef(() => DossierOverviewScreenComponent) }],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class DossierOverviewScreenComponent extends ListingComponent<File> implements OnInit, OnDestroy, OnDetach, OnAttach {
|
export class DossierOverviewScreenComponent extends ListingComponent<File> implements OnInit, OnDestroy, OnAttach {
|
||||||
readonly listingModes = ListingModes;
|
readonly listingModes = ListingModes;
|
||||||
readonly circleButtonTypes = CircleButtonTypes;
|
readonly circleButtonTypes = CircleButtonTypes;
|
||||||
readonly tableHeaderLabel = _('dossier-overview.table-header.title');
|
readonly tableHeaderLabel = _('dossier-overview.table-header.title');
|
||||||
@ -78,21 +76,21 @@ export class DossierOverviewScreenComponent extends ListingComponent<File> imple
|
|||||||
constructor(
|
constructor(
|
||||||
protected readonly _injector: Injector,
|
protected readonly _injector: Injector,
|
||||||
private readonly _router: Router,
|
private readonly _router: Router,
|
||||||
readonly permissionsService: PermissionsService,
|
|
||||||
private readonly _loadingService: LoadingService,
|
private readonly _loadingService: LoadingService,
|
||||||
private readonly _dossiersService: DossiersService,
|
private readonly _dossiersService: DossiersService,
|
||||||
private readonly _dossierTemplatesService: DossierTemplatesService,
|
private readonly _dossierTemplatesService: DossierTemplatesService,
|
||||||
private readonly _appConfigService: AppConfigService,
|
|
||||||
private readonly _fileUploadService: FileUploadService,
|
private readonly _fileUploadService: FileUploadService,
|
||||||
private readonly _filesService: FilesService,
|
private readonly _filesService: FilesService,
|
||||||
private readonly _statusOverlayService: StatusOverlayService,
|
private readonly _statusOverlayService: StatusOverlayService,
|
||||||
private readonly _fileDropOverlayService: FileDropOverlayService,
|
private readonly _fileDropOverlayService: FileDropOverlayService,
|
||||||
private readonly _dossierAttributesService: DossierAttributesService,
|
private readonly _dossierAttributesService: DossierAttributesService,
|
||||||
private readonly _fileAttributesService: FileAttributesService,
|
private readonly _fileAttributesService: FileAttributesService,
|
||||||
readonly configService: ConfigService,
|
|
||||||
private readonly _userPreferenceService: UserPreferenceService,
|
private readonly _userPreferenceService: UserPreferenceService,
|
||||||
private readonly _fileMapService: FilesMapService,
|
private readonly _fileMapService: FilesMapService,
|
||||||
activatedRoute: ActivatedRoute,
|
private readonly _errorService: ErrorService,
|
||||||
|
readonly permissionsService: PermissionsService,
|
||||||
|
readonly configService: ConfigService,
|
||||||
|
readonly activatedRoute: ActivatedRoute,
|
||||||
) {
|
) {
|
||||||
super(_injector);
|
super(_injector);
|
||||||
this.dossierId = activatedRoute.snapshot.paramMap.get('dossierId');
|
this.dossierId = activatedRoute.snapshot.paramMap.get('dossierId');
|
||||||
@ -118,6 +116,8 @@ export class DossierOverviewScreenComponent extends ListingComponent<File> imple
|
|||||||
async ngOnInit(): Promise<void> {
|
async ngOnInit(): Promise<void> {
|
||||||
this._loadEntitiesFromState();
|
this._loadEntitiesFromState();
|
||||||
|
|
||||||
|
this._setRemovableSubscriptions();
|
||||||
|
|
||||||
this.addSubscription = this._fileMapService
|
this.addSubscription = this._fileMapService
|
||||||
.get$(this.dossierId)
|
.get$(this.dossierId)
|
||||||
.pipe(
|
.pipe(
|
||||||
@ -128,18 +128,17 @@ export class DossierOverviewScreenComponent extends ListingComponent<File> imple
|
|||||||
|
|
||||||
this._fileDropOverlayService.initFileDropHandling(this.dossierId);
|
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.addSubscription = this.configService.listingMode$.subscribe(() => {
|
||||||
this._computeAllFilters();
|
this._computeAllFilters();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.addSubscription = this._dossiersService.dossierFileChanges$
|
||||||
|
.pipe(
|
||||||
|
filter(dossierId => dossierId === this.dossierId),
|
||||||
|
switchMap(dossierId => this._filesService.loadAll(dossierId)),
|
||||||
|
)
|
||||||
|
.subscribe();
|
||||||
|
|
||||||
this.addSubscription = this._dossierTemplatesService
|
this.addSubscription = this._dossierTemplatesService
|
||||||
.getEntityChanged$(this.currentDossier.dossierTemplateId)
|
.getEntityChanged$(this.currentDossier.dossierTemplateId)
|
||||||
.pipe(
|
.pipe(
|
||||||
@ -166,11 +165,10 @@ export class DossierOverviewScreenComponent extends ListingComponent<File> imple
|
|||||||
|
|
||||||
ngOnAttach() {
|
ngOnAttach() {
|
||||||
this._fileDropOverlayService.initFileDropHandling(this.dossierId);
|
this._fileDropOverlayService.initFileDropHandling(this.dossierId);
|
||||||
|
this._setRemovableSubscriptions();
|
||||||
this._tableComponent?.scrollToLastIndex();
|
this._tableComponent?.scrollToLastIndex();
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDetach() {}
|
|
||||||
|
|
||||||
forceReanalysisAction($event: LongPressEvent) {
|
forceReanalysisAction($event: LongPressEvent) {
|
||||||
this.analysisForced = !$event.touchEnd && this._userPreferenceService.areDevFeaturesEnabled;
|
this.analysisForced = !$event.touchEnd && this._userPreferenceService.areDevFeaturesEnabled;
|
||||||
}
|
}
|
||||||
@ -192,8 +190,18 @@ export class DossierOverviewScreenComponent extends ListingComponent<File> imple
|
|||||||
(this._fileInput as any).nativeElement.value = null;
|
(this._fileInput as any).nativeElement.value = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
recentlyModifiedChecker = (file: File) =>
|
private _setRemovableSubscriptions(): void {
|
||||||
moment(file.lastUpdated).add(this._appConfigService.values.RECENT_PERIOD_IN_HOURS, 'hours').isAfter(moment());
|
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 {
|
private _updateFileAttributes(): void {
|
||||||
this._fileAttributeConfigs =
|
this._fileAttributeConfigs =
|
||||||
@ -204,11 +212,6 @@ export class DossierOverviewScreenComponent extends ListingComponent<File> imple
|
|||||||
this._computeAllFilters();
|
this._computeAllFilters();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _reloadFiles() {
|
|
||||||
await this._filesService.loadAll(this.dossierId).toPromise();
|
|
||||||
this._computeAllFilters();
|
|
||||||
}
|
|
||||||
|
|
||||||
private _loadEntitiesFromState() {
|
private _loadEntitiesFromState() {
|
||||||
this.currentDossier = this._dossiersService.find(this.dossierId);
|
this.currentDossier = this._dossiersService.find(this.dossierId);
|
||||||
this._computeAllFilters();
|
this._computeAllFilters();
|
||||||
|
|||||||
@ -38,10 +38,10 @@ export class DossiersListingDetailsComponent {
|
|||||||
|
|
||||||
private async _toDossierChartData(dossiers: Dossier[]): Promise<DoughnutChartConfig[]> {
|
private async _toDossierChartData(dossiers: Dossier[]): Promise<DoughnutChartConfig[]> {
|
||||||
// TODO: deleted dossiers count should come with stats
|
// TODO: deleted dossiers count should come with stats
|
||||||
const deletedDossiers = await this.dossiersService.getDeleted();
|
// const deletedDossiers = await this.dossiersService.getDeleted();
|
||||||
return [
|
return [
|
||||||
{ value: dossiers.length, color: 'ACTIVE', label: _('active') },
|
{ value: dossiers.length, color: 'ACTIVE', label: _('active') },
|
||||||
{ value: deletedDossiers.length, color: 'DELETED', label: _('archived') },
|
// { value: deletedDossiers.length, color: 'DELETED', label: _('archived') },
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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 { Dossier } from '@red/domain';
|
||||||
import { UserService } from '@services/user.service';
|
import { UserService } from '@services/user.service';
|
||||||
import { PermissionsService } from '@services/permissions.service';
|
import { PermissionsService } from '@services/permissions.service';
|
||||||
import { TranslateChartService } from '@services/translate-chart.service';
|
import { TranslateChartService } from '@services/translate-chart.service';
|
||||||
import { timer } from 'rxjs';
|
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { DossiersDialogService } from '../../../services/dossiers-dialog.service';
|
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 { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||||
import { ConfigService } from '../config.service';
|
import { ConfigService } from '../config.service';
|
||||||
import { DossiersService } from '@services/entity-services/dossiers.service';
|
import { DossiersService } from '@services/entity-services/dossiers.service';
|
||||||
import { FilesService } from '@services/entity-services/files.service';
|
import { FilesService } from '@services/entity-services/files.service';
|
||||||
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
|
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
|
||||||
import { switchMap, tap } from 'rxjs/operators';
|
import { tap } from 'rxjs/operators';
|
||||||
import { CHANGED_CHECK_INTERVAL } from '@utils/constants';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
templateUrl: './dossiers-listing-screen.component.html',
|
templateUrl: './dossiers-listing-screen.component.html',
|
||||||
@ -25,7 +23,7 @@ import { CHANGED_CHECK_INTERVAL } from '@utils/constants';
|
|||||||
],
|
],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class DossiersListingScreenComponent extends ListingComponent<Dossier> implements OnInit, OnDestroy, OnAttach, OnDetach {
|
export class DossiersListingScreenComponent extends ListingComponent<Dossier> implements OnInit, OnAttach {
|
||||||
readonly currentUser = this._userService.currentUser;
|
readonly currentUser = this._userService.currentUser;
|
||||||
readonly tableColumnConfigs = this._configService.tableConfig;
|
readonly tableColumnConfigs = this._configService.tableConfig;
|
||||||
readonly tableHeaderLabel = _('dossier-listing.table-header.title');
|
readonly tableHeaderLabel = _('dossier-listing.table-header.title');
|
||||||
@ -57,22 +55,13 @@ export class DossiersListingScreenComponent extends ListingComponent<Dossier> im
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
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();
|
this.addSubscription = this._dossiersService.all$.pipe(tap(() => this._computeAllFilters())).subscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnAttach(): void {
|
ngOnAttach(): void {
|
||||||
this.ngOnInit();
|
|
||||||
this._tableComponent?.scrollToLastIndex();
|
this._tableComponent?.scrollToLastIndex();
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDetach(): void {
|
|
||||||
this.ngOnDestroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
openAddDossierDialog(): void {
|
openAddDossierDialog(): void {
|
||||||
this._dialogService.openDialog('addDossier', null, null, async (addResponse: { dossier: Dossier; addMembers: boolean }) => {
|
this._dialogService.openDialog('addDossier', null, null, async (addResponse: { dossier: Dossier; addMembers: boolean }) => {
|
||||||
await this._router.navigate([addResponse.dossier.routerLink]);
|
await this._router.navigate([addResponse.dossier.routerLink]);
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { Component, EventEmitter, Input, OnChanges, Output } from '@angular/core
|
|||||||
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
||||||
import { PermissionsService } from '@services/permissions.service';
|
import { PermissionsService } from '@services/permissions.service';
|
||||||
import { AnnotationPermissions } from '@models/file/annotation.permissions';
|
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 { WebViewerInstance } from '@pdftron/webviewer';
|
||||||
import { UserService } from '@services/user.service';
|
import { UserService } from '@services/user.service';
|
||||||
import { Dossier, File } from '@red/domain';
|
import { Dossier, File } from '@red/domain';
|
||||||
|
|||||||
@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
<div class="comment-actions">
|
<div class="comment-actions">
|
||||||
<iqser-circle-button
|
<iqser-circle-button
|
||||||
(action)="deleteComment(comment)"
|
(action)="deleteComment($event, comment)"
|
||||||
*ngIf="permissionsService.canDeleteComment(comment, file)"
|
*ngIf="permissionsService.canDeleteComment(comment, file)"
|
||||||
[iconSize]="10"
|
[iconSize]="10"
|
||||||
[size]="20"
|
[size]="20"
|
||||||
|
|||||||
@ -1,13 +1,15 @@
|
|||||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, HostBinding, Input, ViewChild } from '@angular/core';
|
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, HostBinding, Input, OnChanges, ViewChild } from '@angular/core';
|
||||||
import { File, IComment } from '@red/domain';
|
import { File, IComment } from '@red/domain';
|
||||||
import { ManualAnnotationService } from '../../../../services/manual-annotation.service';
|
import { ManualAnnotationService } from '../../../../services/manual-annotation.service';
|
||||||
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
||||||
import { UserService } from '@services/user.service';
|
import { UserService } from '@services/user.service';
|
||||||
import { PermissionsService } from '@services/permissions.service';
|
import { PermissionsService } from '@services/permissions.service';
|
||||||
import { InputWithActionComponent, trackBy } from '@iqser/common-ui';
|
import { AutoUnsubscribe, InputWithActionComponent, LoadingService, trackBy } from '@iqser/common-ui';
|
||||||
import { FilesMapService } from '@services/entity-services/files-map.service';
|
import { FilesMapService } from '@services/entity-services/files-map.service';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
|
import { CommentingService } from '../../services/commenting.service';
|
||||||
|
import { tap } from 'rxjs/operators';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'redaction-comments',
|
selector: 'redaction-comments',
|
||||||
@ -15,11 +17,11 @@ import { Observable } from 'rxjs';
|
|||||||
styleUrls: ['./comments.component.scss'],
|
styleUrls: ['./comments.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class CommentsComponent {
|
export class CommentsComponent extends AutoUnsubscribe implements OnChanges {
|
||||||
@Input() annotation: AnnotationWrapper;
|
@Input() annotation: AnnotationWrapper;
|
||||||
readonly trackBy = trackBy();
|
readonly trackBy = trackBy();
|
||||||
readonly file$: Observable<File>;
|
readonly file$: Observable<File>;
|
||||||
@HostBinding('class.hidden') private _hidden = true;
|
@HostBinding('class.hidden') _hidden = true;
|
||||||
@ViewChild(InputWithActionComponent) private readonly _input: InputWithActionComponent;
|
@ViewChild(InputWithActionComponent) private readonly _input: InputWithActionComponent;
|
||||||
private readonly _fileId: string;
|
private readonly _fileId: string;
|
||||||
private readonly _dossierId: string;
|
private readonly _dossierId: string;
|
||||||
@ -28,49 +30,59 @@ export class CommentsComponent {
|
|||||||
readonly permissionsService: PermissionsService,
|
readonly permissionsService: PermissionsService,
|
||||||
private readonly _userService: UserService,
|
private readonly _userService: UserService,
|
||||||
private readonly _manualAnnotationService: ManualAnnotationService,
|
private readonly _manualAnnotationService: ManualAnnotationService,
|
||||||
private readonly _changeDetectorRef: ChangeDetectorRef,
|
private readonly _commentingService: CommentingService,
|
||||||
|
private readonly _loadingService: LoadingService,
|
||||||
|
private readonly _changeRef: ChangeDetectorRef,
|
||||||
readonly filesMapService: FilesMapService,
|
readonly filesMapService: FilesMapService,
|
||||||
activatedRoute: ActivatedRoute,
|
activatedRoute: ActivatedRoute,
|
||||||
) {
|
) {
|
||||||
|
super();
|
||||||
this._fileId = activatedRoute.snapshot.paramMap.get('fileId');
|
this._fileId = activatedRoute.snapshot.paramMap.get('fileId');
|
||||||
this._dossierId = activatedRoute.snapshot.paramMap.get('dossierId');
|
this._dossierId = activatedRoute.snapshot.paramMap.get('dossierId');
|
||||||
this.file$ = filesMapService.watch$(this._dossierId, this._fileId);
|
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<void> {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._manualAnnotationService
|
this._loadingService.start();
|
||||||
|
const commentId = await this._manualAnnotationService
|
||||||
.addComment(value, this.annotation.id, this._dossierId, this._fileId)
|
.addComment(value, this.annotation.id, this._dossierId, this._fileId)
|
||||||
.toPromise()
|
.toPromise();
|
||||||
.then(commentId => {
|
this.annotation.comments.push({
|
||||||
this.annotation.comments.push({
|
text: value,
|
||||||
text: value,
|
id: commentId,
|
||||||
id: commentId,
|
annotationId: this.annotation.id,
|
||||||
annotationId: this.annotation.id,
|
user: this._userService.currentUser.id,
|
||||||
user: this._userService.currentUser.id,
|
});
|
||||||
});
|
this._input.reset();
|
||||||
this._input.reset();
|
this._changeRef.markForCheck();
|
||||||
this._changeDetectorRef.markForCheck();
|
this._loadingService.stop();
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleExpandComments($event?: MouseEvent): void {
|
toggleExpandComments($event?: MouseEvent): void {
|
||||||
$event?.stopPropagation();
|
$event?.stopPropagation();
|
||||||
this._hidden = !this._hidden;
|
this._commentingService.toggle(this.annotation.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteComment(comment: IComment): void {
|
async deleteComment($event: MouseEvent, comment: IComment): Promise<void> {
|
||||||
this._manualAnnotationService
|
$event.stopPropagation();
|
||||||
.deleteComment(comment.id, this.annotation.id, this._dossierId, this._fileId)
|
this._loadingService.start();
|
||||||
.toPromise()
|
await this._manualAnnotationService.deleteComment(comment.id, this.annotation.id, this._dossierId, this._fileId).toPromise();
|
||||||
.then(() => {
|
this.annotation.comments.splice(this.annotation.comments.indexOf(comment), 1);
|
||||||
this.annotation.comments.splice(this.annotation.comments.indexOf(comment), 1);
|
this._changeRef.markForCheck();
|
||||||
if (!this.annotation.comments.length) {
|
this._loadingService.stop();
|
||||||
this._hidden = true;
|
|
||||||
}
|
|
||||||
this._changeDetectorRef.markForCheck();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -38,13 +38,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="!isReadOnly && file.isProcessing" class="justify-center read-only d-flex">
|
|
||||||
<div class="flex-align-items-center">
|
|
||||||
<span [translate]="'file-status.processing'" class="read-only-text"></span>
|
|
||||||
<mat-progress-bar [mode]="'indeterminate'" class="w-100"></mat-progress-bar>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div *ngIf="multiSelectActive$ | async" class="multi-select">
|
<div *ngIf="multiSelectActive$ | async" class="multi-select">
|
||||||
<div class="selected-wrapper">
|
<div class="selected-wrapper">
|
||||||
<iqser-round-checkbox
|
<iqser-round-checkbox
|
||||||
@ -103,7 +96,6 @@
|
|||||||
[file]="file"
|
[file]="file"
|
||||||
[number]="pageNumber"
|
[number]="pageNumber"
|
||||||
[showDottedIcon]="hasOnlyManualRedactionsAndIsExcluded(pageNumber)"
|
[showDottedIcon]="hasOnlyManualRedactionsAndIsExcluded(pageNumber)"
|
||||||
[viewedPages]="viewedPages"
|
|
||||||
></redaction-page-indicator>
|
></redaction-page-indicator>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -224,9 +216,9 @@
|
|||||||
|
|
||||||
<ng-template #annotationFilterActionTemplate let-filter="filter">
|
<ng-template #annotationFilterActionTemplate let-filter="filter">
|
||||||
<iqser-circle-button
|
<iqser-circle-button
|
||||||
(action)="toggleSkipped.emit($event)"
|
(action)="skippedService.toggleSkipped($event)"
|
||||||
*ngIf="filter.id === 'skipped'"
|
*ngIf="filter.id === 'skipped'"
|
||||||
[icon]="hideSkipped ? 'red:visibility-off' : 'red:visibility'"
|
[icon]="(skippedService.hideSkipped$ | async) ? 'red:visibility-off' : 'red:visibility'"
|
||||||
[type]="circleButtonTypes.dark"
|
[type]="circleButtonTypes.dark"
|
||||||
></iqser-circle-button>
|
></iqser-circle-button>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|||||||
@ -28,10 +28,11 @@ import { PermissionsService } from '@services/permissions.service';
|
|||||||
import { WebViewerInstance } from '@pdftron/webviewer';
|
import { WebViewerInstance } from '@pdftron/webviewer';
|
||||||
import { BehaviorSubject, combineLatest, Observable } from 'rxjs';
|
import { BehaviorSubject, combineLatest, Observable } from 'rxjs';
|
||||||
import { map, tap } from 'rxjs/operators';
|
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 { ExcludedPagesService } from '../../services/excluded-pages.service';
|
||||||
import { MultiSelectService } from '../../services/multi-select.service';
|
import { MultiSelectService } from '../../services/multi-select.service';
|
||||||
import { DocumentInfoService } from '../../services/document-info.service';
|
import { DocumentInfoService } from '../../services/document-info.service';
|
||||||
|
import { SkippedService } from '../../services/skipped.service';
|
||||||
|
|
||||||
const COMMAND_KEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Escape'];
|
const COMMAND_KEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Escape'];
|
||||||
const ALL_HOTKEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'];
|
const ALL_HOTKEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'];
|
||||||
@ -51,16 +52,13 @@ export class FileWorkloadComponent {
|
|||||||
@Input() activeViewerPage: number;
|
@Input() activeViewerPage: number;
|
||||||
@Input() shouldDeselectAnnotationsOnPageChange: boolean;
|
@Input() shouldDeselectAnnotationsOnPageChange: boolean;
|
||||||
@Input() dialogRef: MatDialogRef<unknown>;
|
@Input() dialogRef: MatDialogRef<unknown>;
|
||||||
@Input() viewedPages: IViewedPage[];
|
|
||||||
@Input() file!: File;
|
@Input() file!: File;
|
||||||
@Input() hideSkipped: boolean;
|
|
||||||
@Input() annotationActionsTemplate: TemplateRef<unknown>;
|
@Input() annotationActionsTemplate: TemplateRef<unknown>;
|
||||||
@Input() viewer: WebViewerInstance;
|
@Input() viewer: WebViewerInstance;
|
||||||
@Output() readonly shouldDeselectAnnotationsOnPageChangeChange = new EventEmitter<boolean>();
|
@Output() readonly shouldDeselectAnnotationsOnPageChangeChange = new EventEmitter<boolean>();
|
||||||
@Output() readonly selectAnnotations = new EventEmitter<AnnotationWrapper[]>();
|
@Output() readonly selectAnnotations = new EventEmitter<AnnotationWrapper[]>();
|
||||||
@Output() readonly deselectAnnotations = new EventEmitter<AnnotationWrapper[]>();
|
@Output() readonly deselectAnnotations = new EventEmitter<AnnotationWrapper[]>();
|
||||||
@Output() readonly selectPage = new EventEmitter<number>();
|
@Output() readonly selectPage = new EventEmitter<number>();
|
||||||
@Output() readonly toggleSkipped = new EventEmitter<MouseEvent>();
|
|
||||||
@Output() readonly annotationsChanged = new EventEmitter<AnnotationWrapper>();
|
@Output() readonly annotationsChanged = new EventEmitter<AnnotationWrapper>();
|
||||||
displayedPages: number[] = [];
|
displayedPages: number[] = [];
|
||||||
pagesPanelActive = true;
|
pagesPanelActive = true;
|
||||||
@ -76,6 +74,7 @@ export class FileWorkloadComponent {
|
|||||||
readonly excludedPagesService: ExcludedPagesService,
|
readonly excludedPagesService: ExcludedPagesService,
|
||||||
readonly multiSelectService: MultiSelectService,
|
readonly multiSelectService: MultiSelectService,
|
||||||
readonly documentInfoService: DocumentInfoService,
|
readonly documentInfoService: DocumentInfoService,
|
||||||
|
readonly skippedService: SkippedService,
|
||||||
private readonly _permissionsService: PermissionsService,
|
private readonly _permissionsService: PermissionsService,
|
||||||
private readonly _changeDetectorRef: ChangeDetectorRef,
|
private readonly _changeDetectorRef: ChangeDetectorRef,
|
||||||
private readonly _filterService: FilterService,
|
private readonly _filterService: FilterService,
|
||||||
@ -225,7 +224,7 @@ export class FileWorkloadComponent {
|
|||||||
|
|
||||||
scrollAnnotationsToPage(page: number, mode: 'always' | 'if-needed' = 'if-needed'): void {
|
scrollAnnotationsToPage(page: number, mode: 'always' | 'if-needed' = 'if-needed'): void {
|
||||||
if (this._annotationsElement) {
|
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);
|
FileWorkloadComponent._scrollToFirstElement(elements, mode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -235,7 +234,7 @@ export class FileWorkloadComponent {
|
|||||||
if (!this.selectedAnnotations || this.selectedAnnotations.length === 0 || !this._annotationsElement) {
|
if (!this.selectedAnnotations || this.selectedAnnotations.length === 0 || !this._annotationsElement) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const elements = this._annotationsElement.nativeElement.querySelectorAll(
|
const elements: HTMLElement[] = this._annotationsElement.nativeElement.querySelectorAll(
|
||||||
`div[annotation-id="${this._firstSelectedAnnotation?.id}"].active`,
|
`div[annotation-id="${this._firstSelectedAnnotation?.id}"].active`,
|
||||||
);
|
);
|
||||||
FileWorkloadComponent._scrollToFirstElement(elements);
|
FileWorkloadComponent._scrollToFirstElement(elements);
|
||||||
@ -412,7 +411,7 @@ export class FileWorkloadComponent {
|
|||||||
|
|
||||||
private _scrollQuickNavigationToPage(page: number) {
|
private _scrollQuickNavigationToPage(page: number) {
|
||||||
if (this._quickNavigationElement) {
|
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);
|
FileWorkloadComponent._scrollToFirstElement(elements);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { ViewedPagesService } from '@services/entity-services/viewed-pages.servi
|
|||||||
import { File, IViewedPage } from '@red/domain';
|
import { File, IViewedPage } from '@red/domain';
|
||||||
import { AutoUnsubscribe } from '@iqser/common-ui';
|
import { AutoUnsubscribe } from '@iqser/common-ui';
|
||||||
import { FilesMapService } from '@services/entity-services/files-map.service';
|
import { FilesMapService } from '@services/entity-services/files-map.service';
|
||||||
|
import { FilePreviewStateService } from '../../services/file-preview-state.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'redaction-page-indicator',
|
selector: 'redaction-page-indicator',
|
||||||
@ -18,7 +19,6 @@ export class PageIndicatorComponent extends AutoUnsubscribe implements OnDestroy
|
|||||||
@Input() active = false;
|
@Input() active = false;
|
||||||
@Input() showDottedIcon = false;
|
@Input() showDottedIcon = false;
|
||||||
@Input() number: number;
|
@Input() number: number;
|
||||||
@Input() viewedPages: IViewedPage[];
|
|
||||||
@Input() activeSelection = false;
|
@Input() activeSelection = false;
|
||||||
|
|
||||||
@Output() readonly pageSelected = new EventEmitter<number>();
|
@Output() readonly pageSelected = new EventEmitter<number>();
|
||||||
@ -33,25 +33,17 @@ export class PageIndicatorComponent extends AutoUnsubscribe implements OnDestroy
|
|||||||
private readonly _configService: ConfigService,
|
private readonly _configService: ConfigService,
|
||||||
private readonly _changeDetectorRef: ChangeDetectorRef,
|
private readonly _changeDetectorRef: ChangeDetectorRef,
|
||||||
private readonly _permissionService: PermissionsService,
|
private readonly _permissionService: PermissionsService,
|
||||||
|
private readonly _stateService: FilePreviewStateService,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
get activePage() {
|
get activePage() {
|
||||||
return this.viewedPages?.find(p => p.page === this.number);
|
return this._viewedPages.find(p => p.page === this.number);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _setReadState() {
|
private get _viewedPages(): IViewedPage[] {
|
||||||
const readBefore = this.read;
|
return this._stateService.fileData?.viewedPages || [];
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnChanges() {
|
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() {
|
private async _markPageRead() {
|
||||||
await this._viewedPagesService.addPage({ page: this.number }, this.file.dossierId, this.file.fileId).toPromise();
|
await this._viewedPagesService.addPage({ page: this.number }, this.file.dossierId, this.file.fileId).toPromise();
|
||||||
if (this.activePage) {
|
if (this.activePage) {
|
||||||
this.activePage.showAsUnseen = false;
|
this.activePage.showAsUnseen = false;
|
||||||
} else {
|
} else {
|
||||||
this.viewedPages?.push({ page: this.number, fileId: this.file.fileId });
|
this._viewedPages.push({ page: this.number, fileId: this.file.fileId });
|
||||||
}
|
}
|
||||||
this._setReadState();
|
this._setReadState();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _markPageUnread() {
|
private async _markPageUnread() {
|
||||||
await this._viewedPagesService.removePage(this.file.dossierId, this.file.fileId, this.number).toPromise();
|
await this._viewedPagesService.removePage(this.file.dossierId, this.file.fileId, this.number).toPromise();
|
||||||
this.viewedPages?.splice(
|
this._viewedPages.splice(
|
||||||
this.viewedPages?.findIndex(p => p.page === this.number),
|
this._viewedPages.findIndex(p => p.page === this.number),
|
||||||
1,
|
1,
|
||||||
);
|
);
|
||||||
this._setReadState();
|
this._setReadState();
|
||||||
|
|||||||
@ -22,12 +22,12 @@ import {
|
|||||||
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
||||||
import { ManualAnnotationService } from '../../../../services/manual-annotation.service';
|
import { ManualAnnotationService } from '../../../../services/manual-annotation.service';
|
||||||
import { environment } from '@environments/environment';
|
import { environment } from '@environments/environment';
|
||||||
import { AnnotationDrawService } from '../../../../services/annotation-draw.service';
|
import { AnnotationDrawService } from '../../services/annotation-draw.service';
|
||||||
import { AnnotationActionsService } from '../../../../services/annotation-actions.service';
|
import { AnnotationActionsService } from '../../services/annotation-actions.service';
|
||||||
import { UserPreferenceService } from '@services/user-preference.service';
|
import { UserPreferenceService } from '@services/user-preference.service';
|
||||||
import { BASE_HREF } from '../../../../../../tokens';
|
import { BASE_HREF } from '../../../../../../tokens';
|
||||||
import { ConfigService } from '@services/config.service';
|
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 { DossiersDialogService } from '../../../../services/dossiers-dialog.service';
|
||||||
import { loadCompareDocumentWrapper } from '../../../../utils/compare-mode.utils';
|
import { loadCompareDocumentWrapper } from '../../../../utils/compare-mode.utils';
|
||||||
import { PdfViewerUtils } from '../../../../utils/pdf-viewer.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 { toPosition } from '../../../../utils/pdf-calculation.utils';
|
||||||
import { ViewModeService } from '../../services/view-mode.service';
|
import { ViewModeService } from '../../services/view-mode.service';
|
||||||
import { MultiSelectService } from '../../services/multi-select.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 Tools = Core.Tools;
|
||||||
import TextTool = Tools.TextTool;
|
import TextTool = Tools.TextTool;
|
||||||
import Annotation = Core.Annotations.Annotation;
|
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 = {
|
const dataElements = {
|
||||||
ADD_REDACTION: 'add-redaction',
|
ADD_REDACTION: 'add-redaction',
|
||||||
ADD_DICTIONARY: 'add-dictionary',
|
ADD_DICTIONARY: 'add-dictionary',
|
||||||
@ -61,8 +63,7 @@ const dataElements = {
|
|||||||
templateUrl: './pdf-viewer.component.html',
|
templateUrl: './pdf-viewer.component.html',
|
||||||
styleUrls: ['./pdf-viewer.component.scss'],
|
styleUrls: ['./pdf-viewer.component.scss'],
|
||||||
})
|
})
|
||||||
export class PdfViewerComponent implements OnInit, OnChanges {
|
export class PdfViewerComponent extends AutoUnsubscribe implements OnInit, OnChanges {
|
||||||
@Input() fileData: Blob;
|
|
||||||
@Input() file: File;
|
@Input() file: File;
|
||||||
@Input() dossier: Dossier;
|
@Input() dossier: Dossier;
|
||||||
@Input() canPerformActions = false;
|
@Input() canPerformActions = false;
|
||||||
@ -95,9 +96,12 @@ export class PdfViewerComponent implements OnInit, OnChanges {
|
|||||||
private readonly _annotationActionsService: AnnotationActionsService,
|
private readonly _annotationActionsService: AnnotationActionsService,
|
||||||
private readonly _configService: ConfigService,
|
private readonly _configService: ConfigService,
|
||||||
private readonly _loadingService: LoadingService,
|
private readonly _loadingService: LoadingService,
|
||||||
|
private readonly _stateService: FilePreviewStateService,
|
||||||
readonly viewModeService: ViewModeService,
|
readonly viewModeService: ViewModeService,
|
||||||
readonly multiSelectService: MultiSelectService,
|
readonly multiSelectService: MultiSelectService,
|
||||||
) {}
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
private get _toggleTooltipsBtnTitle(): string {
|
private get _toggleTooltipsBtnTitle(): string {
|
||||||
return this._translateService.instant(_('pdf-viewer.toggle-tooltips'), {
|
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() {
|
async ngOnInit() {
|
||||||
this._setReadyAndInitialState = this._setReadyAndInitialState.bind(this);
|
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 {
|
ngOnChanges(changes: SimpleChanges): void {
|
||||||
@ -115,16 +137,12 @@ export class PdfViewerComponent implements OnInit, OnChanges {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (changes.fileData) {
|
|
||||||
this._loadDocument();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (changes.canPerformActions) {
|
if (changes.canPerformActions) {
|
||||||
this._handleCustomActions();
|
this._handleCustomActions();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uploadFile(files: any) {
|
uploadFile(files: FileList) {
|
||||||
const fileToCompare = files[0];
|
const fileToCompare = files[0];
|
||||||
this.compareFileInput.nativeElement.value = null;
|
this.compareFileInput.nativeElement.value = null;
|
||||||
|
|
||||||
@ -140,7 +158,7 @@ export class PdfViewerComponent implements OnInit, OnChanges {
|
|||||||
await pdfNet.initialize(environment.licenseKey ? atob(environment.licenseKey) : null);
|
await pdfNet.initialize(environment.licenseKey ? atob(environment.licenseKey) : null);
|
||||||
|
|
||||||
const compareDocument = await pdfNet.PDFDoc.createFromBuffer(fileReader.result as ArrayBuffer);
|
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 () => {
|
const loadCompareDocument = async () => {
|
||||||
this._loadingService.start();
|
this._loadingService.start();
|
||||||
@ -153,7 +171,7 @@ export class PdfViewerComponent implements OnInit, OnChanges {
|
|||||||
this.instance,
|
this.instance,
|
||||||
this.file,
|
this.file,
|
||||||
() => {
|
() => {
|
||||||
this.viewModeService.set('COMPARE');
|
this.viewModeService.compareMode = true;
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
this.utils.navigateToPage(1);
|
this.utils.navigateToPage(1);
|
||||||
@ -190,10 +208,10 @@ export class PdfViewerComponent implements OnInit, OnChanges {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async closeCompareMode() {
|
async closeCompareMode() {
|
||||||
this.viewModeService.set('STANDARD');
|
this.viewModeService.compareMode = false;
|
||||||
const pdfNet = this.instance.Core.PDFNet;
|
const pdfNet = this.instance.Core.PDFNet;
|
||||||
await pdfNet.initialize(environment.licenseKey ? atob(environment.licenseKey) : null);
|
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, {
|
this.instance.UI.loadDocument(currentDocument, {
|
||||||
filename: this.file ? this.file.filename : 'document.pdf',
|
filename: this.file ? this.file.filename : 'document.pdf',
|
||||||
});
|
});
|
||||||
@ -202,7 +220,7 @@ export class PdfViewerComponent implements OnInit, OnChanges {
|
|||||||
this.utils.navigateToPage(1);
|
this.utils.navigateToPage(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadViewer() {
|
private async _loadViewer() {
|
||||||
this.instance = await WebViewer(
|
this.instance = await WebViewer(
|
||||||
{
|
{
|
||||||
licenseKey: environment.licenseKey ? atob(environment.licenseKey) : null,
|
licenseKey: environment.licenseKey ? atob(environment.licenseKey) : null,
|
||||||
@ -211,7 +229,7 @@ export class PdfViewerComponent implements OnInit, OnChanges {
|
|||||||
css: this._convertPath('/assets/pdftron/stylesheet.css'),
|
css: this._convertPath('/assets/pdftron/stylesheet.css'),
|
||||||
backendType: 'ems',
|
backendType: 'ems',
|
||||||
},
|
},
|
||||||
this.viewer.nativeElement,
|
this.viewer.nativeElement as HTMLElement,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.documentViewer = this.instance.Core.documentViewer;
|
this.documentViewer = this.instance.Core.documentViewer;
|
||||||
@ -223,7 +241,7 @@ export class PdfViewerComponent implements OnInit, OnChanges {
|
|||||||
this.utils.disableHotkeys();
|
this.utils.disableHotkeys();
|
||||||
this._configureTextPopup();
|
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));
|
this.annotationSelected.emit(this.annotationManager.getSelectedAnnotations().map(ann => ann.Id));
|
||||||
if (action === 'deselected') {
|
if (action === 'deselected') {
|
||||||
this._toggleRectangleAnnotationAction(true);
|
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,
|
// when a rectangle is drawn,
|
||||||
// it returns one annotation with tool name 'AnnotationCreateRectangle;
|
// it returns one annotation with tool name 'AnnotationCreateRectangle;
|
||||||
// this will auto select rectangle after drawing
|
// 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) {
|
if (this.shouldDeselectAnnotationsOnPageChange) {
|
||||||
this.utils.deselectAllAnnotations();
|
this.utils.deselectAllAnnotations();
|
||||||
}
|
}
|
||||||
@ -253,9 +271,9 @@ export class PdfViewerComponent implements OnInit, OnChanges {
|
|||||||
|
|
||||||
this.documentViewer.addEventListener('documentLoaded', this._setReadyAndInitialState);
|
this.documentViewer.addEventListener('documentLoaded', this._setReadyAndInitialState);
|
||||||
|
|
||||||
this.documentViewer.addEventListener('keyUp', $event => {
|
this.documentViewer.addEventListener('keyUp', ($event: KeyboardEvent) => {
|
||||||
// arrows and full-screen
|
// 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') {
|
if ($event.key.startsWith('Arrow') || $event.key === 'f') {
|
||||||
this._ngZone.run(() => {
|
this._ngZone.run(() => {
|
||||||
this.keyUp.emit($event);
|
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.preventDefault();
|
||||||
$event.stopPropagation();
|
$event.stopPropagation();
|
||||||
}
|
}
|
||||||
@ -293,8 +311,6 @@ export class PdfViewerComponent implements OnInit, OnChanges {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this._loadDocument();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _setInitialDisplayMode() {
|
private _setInitialDisplayMode() {
|
||||||
@ -354,13 +370,14 @@ export class PdfViewerComponent implements OnInit, OnChanges {
|
|||||||
type: 'actionButton',
|
type: 'actionButton',
|
||||||
element: 'tooltips',
|
element: 'tooltips',
|
||||||
dataElement: dataElements.TOGGLE_TOOLTIPS,
|
dataElement: dataElements.TOGGLE_TOOLTIPS,
|
||||||
img: this._convertPath('/assets/icons/general/pdftron-action-toggle-tooltips.svg'),
|
img: this._toggleTooltipsIcon,
|
||||||
title: this._toggleTooltipsBtnTitle,
|
title: this._toggleTooltipsBtnTitle,
|
||||||
onClick: async () => {
|
onClick: async () => {
|
||||||
await this._userPreferenceService.toggleFilePreviewTooltipsPreference();
|
await this._userPreferenceService.toggleFilePreviewTooltipsPreference();
|
||||||
this._updateTooltipsVisibility();
|
this._updateTooltipsVisibility();
|
||||||
this.instance.UI.updateElement(dataElements.TOGGLE_TOOLTIPS, {
|
this.instance.UI.updateElement(dataElements.TOGGLE_TOOLTIPS, {
|
||||||
title: this._toggleTooltipsBtnTitle,
|
title: this._toggleTooltipsBtnTitle,
|
||||||
|
img: this._toggleTooltipsIcon,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -381,36 +398,34 @@ export class PdfViewerComponent implements OnInit, OnChanges {
|
|||||||
const originalHeaderItems = header.getItems();
|
const originalHeaderItems = header.getItems();
|
||||||
originalHeaderItems.splice(8, 0, ...headerItems);
|
originalHeaderItems.splice(8, 0, ...headerItems);
|
||||||
|
|
||||||
if (this._userPreferenceService.areDevFeaturesEnabled) {
|
const compareHeaderItems = [
|
||||||
const devHeaderItems = [
|
{
|
||||||
{
|
type: 'actionButton',
|
||||||
type: 'actionButton',
|
element: 'compare',
|
||||||
element: 'compare',
|
dataElement: dataElements.COMPARE_BUTTON,
|
||||||
dataElement: dataElements.COMPARE_BUTTON,
|
img: this._convertPath('/assets/icons/general/pdftron-action-compare.svg'),
|
||||||
img: this._convertPath('/assets/icons/general/pdftron-action-compare.svg'),
|
title: 'Compare',
|
||||||
title: 'Compare',
|
onClick: () => {
|
||||||
onClick: () => {
|
this.compareFileInput.nativeElement.click();
|
||||||
this.compareFileInput.nativeElement.click();
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
},
|
||||||
type: 'actionButton',
|
{
|
||||||
element: 'closeCompare',
|
type: 'actionButton',
|
||||||
dataElement: dataElements.CLOSE_COMPARE_BUTTON,
|
element: 'closeCompare',
|
||||||
img: this._convertPath('/assets/icons/general/pdftron-action-close-compare.svg'),
|
dataElement: dataElements.CLOSE_COMPARE_BUTTON,
|
||||||
title: 'Leave Compare Mode',
|
img: this._convertPath('/assets/icons/general/pdftron-action-close-compare.svg'),
|
||||||
onClick: async () => {
|
title: 'Leave Compare Mode',
|
||||||
await this.closeCompareMode();
|
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]);
|
this.instance.UI.disableElements([dataElements.CLOSE_COMPARE_BUTTON]);
|
||||||
@ -561,7 +576,7 @@ export class PdfViewerComponent implements OnInit, OnChanges {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _addManualRedactionOfType(type: ManualRedactionEntryType) {
|
private _addManualRedactionOfType(type: ManualRedactionEntryType) {
|
||||||
const selectedQuads = this.documentViewer.getSelectedTextQuads();
|
const selectedQuads: Readonly<Record<string, Core.Math.Quad[]>> = this.documentViewer.getSelectedTextQuads();
|
||||||
const text = this.documentViewer.getSelectedText();
|
const text = this.documentViewer.getSelectedText();
|
||||||
const manualRedaction = this._getManualRedaction(selectedQuads, text, true);
|
const manualRedaction = this._getManualRedaction(selectedQuads, text, true);
|
||||||
this.manualAnnotationRequested.emit(new ManualRedactionEntryWrapper(selectedQuads, manualRedaction, type));
|
this.manualAnnotationRequested.emit(new ManualRedactionEntryWrapper(selectedQuads, manualRedaction, type));
|
||||||
@ -617,7 +632,7 @@ export class PdfViewerComponent implements OnInit, OnChanges {
|
|||||||
for (const quad of quads[key]) {
|
for (const quad of quads[key]) {
|
||||||
const page = parseInt(key, 10);
|
const page = parseInt(key, 10);
|
||||||
const pageHeight = this.documentViewer.getPageHeight(page);
|
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() {
|
private _loadDocument() {
|
||||||
if (!this.fileData) {
|
this.instance.UI.loadDocument(this._stateService.fileData.blob$.value, {
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.instance.UI.loadDocument(this.fileData, {
|
|
||||||
filename: this.file ? this.file.filename : 'document.pdf',
|
filename: this.file ? this.file.filename : 'document.pdf',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -640,7 +651,7 @@ export class PdfViewerComponent implements OnInit, OnChanges {
|
|||||||
this._ngZone.run(() => {
|
this._ngZone.run(() => {
|
||||||
this.utils.ready = true;
|
this.utils.ready = true;
|
||||||
this.viewerReady.emit(this.instance);
|
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.pageChanged.emit(routePageNumber || 1);
|
||||||
this._setInitialDisplayMode();
|
this._setInitialDisplayMode();
|
||||||
this._updateTooltipsVisibility();
|
this._updateTooltipsVisibility();
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<ng-container *ngIf="viewModeService.viewMode$ | async as viewMode">
|
<ng-container *ngIf="viewModeService.viewMode$ | async as viewMode">
|
||||||
<div
|
<button
|
||||||
(click)="switchView.emit('STANDARD')"
|
(click)="switchView.emit('STANDARD')"
|
||||||
[class.active]="viewModeService.isStandard"
|
[class.active]="viewModeService.isStandard"
|
||||||
[matTooltip]="'file-preview.standard-tooltip' | translate"
|
[matTooltip]="'file-preview.standard-tooltip' | translate"
|
||||||
@ -7,27 +7,27 @@
|
|||||||
iqserHelpMode="standard-view"
|
iqserHelpMode="standard-view"
|
||||||
>
|
>
|
||||||
{{ 'file-preview.standard' | translate }}
|
{{ 'file-preview.standard' | translate }}
|
||||||
</div>
|
</button>
|
||||||
|
|
||||||
<div
|
<button
|
||||||
(click)="canSwitchToDeltaView && switchView.emit('DELTA')"
|
(click)="switchView.emit('DELTA')"
|
||||||
[class.active]="viewModeService.isDelta"
|
[class.active]="viewModeService.isDelta"
|
||||||
[class.disabled]="!canSwitchToDeltaView"
|
[disabled]="(canSwitchToDeltaView$ | async) === false"
|
||||||
[matTooltip]="'file-preview.delta-tooltip' | translate"
|
[matTooltip]="'file-preview.delta-tooltip' | translate"
|
||||||
class="red-tab"
|
class="red-tab"
|
||||||
iqserHelpMode="delta-view"
|
iqserHelpMode="delta-view"
|
||||||
>
|
>
|
||||||
{{ 'file-preview.delta' | translate }}
|
{{ 'file-preview.delta' | translate }}
|
||||||
</div>
|
</button>
|
||||||
|
|
||||||
<div
|
<button
|
||||||
(click)="canSwitchToRedactedView && switchView.emit('REDACTED')"
|
(click)="canSwitchToRedactedView && switchView.emit('REDACTED')"
|
||||||
[class.active]="viewModeService.isRedacted"
|
[class.active]="viewModeService.isRedacted"
|
||||||
[class.disabled]="!canSwitchToRedactedView"
|
[disabled]="!canSwitchToRedactedView"
|
||||||
[matTooltip]="'file-preview.redacted-tooltip' | translate"
|
[matTooltip]="'file-preview.redacted-tooltip' | translate"
|
||||||
class="red-tab"
|
class="red-tab"
|
||||||
iqserHelpMode="preview-view"
|
iqserHelpMode="preview-view"
|
||||||
>
|
>
|
||||||
{{ 'file-preview.redacted' | translate }}
|
{{ 'file-preview.redacted' | translate }}
|
||||||
</div>
|
</button>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|||||||
@ -1,10 +1,12 @@
|
|||||||
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core';
|
||||||
import { File, ViewMode } from '@red/domain';
|
import { File, ViewMode } from '@red/domain';
|
||||||
import { ViewModeService } from '../../services/view-mode.service';
|
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({
|
@Component({
|
||||||
selector: 'redaction-view-switch [file] [fileData]',
|
selector: 'redaction-view-switch [file]',
|
||||||
templateUrl: './view-switch.component.html',
|
templateUrl: './view-switch.component.html',
|
||||||
styleUrls: ['./view-switch.component.scss'],
|
styleUrls: ['./view-switch.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
@ -12,19 +14,18 @@ import { FileDataModel } from '@models/file/file-data.model';
|
|||||||
export class ViewSwitchComponent implements OnChanges {
|
export class ViewSwitchComponent implements OnChanges {
|
||||||
@Output() readonly switchView = new EventEmitter<ViewMode>();
|
@Output() readonly switchView = new EventEmitter<ViewMode>();
|
||||||
@Input() file: File;
|
@Input() file: File;
|
||||||
@Input() fileData: FileDataModel;
|
|
||||||
|
|
||||||
canSwitchToDeltaView = false;
|
readonly canSwitchToDeltaView$: Observable<boolean>;
|
||||||
canSwitchToRedactedView = false;
|
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) {
|
ngOnChanges(changes: SimpleChanges) {
|
||||||
if (changes.fileData) {
|
|
||||||
const fileData = changes.fileData.currentValue as FileDataModel;
|
|
||||||
this.canSwitchToDeltaView = fileData?.hasChangeLog;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (changes.file) {
|
if (changes.file) {
|
||||||
const file = changes?.file.currentValue as File;
|
const file = changes?.file.currentValue as File;
|
||||||
this.canSwitchToRedactedView = !file.analysisRequired && !file.excluded;
|
this.canSwitchToRedactedView = !file.analysisRequired && !file.excluded;
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
<ng-container *ngIf="dossier$ | async as dossier">
|
<ng-container *ngIf="dossier$ | async as dossier">
|
||||||
<ng-container *ngIf="file$ | async as file">
|
<ng-container *ngIf="fileData?.file$ | async as file">
|
||||||
<section [class.fullscreen]="fullScreen">
|
<section [class.fullscreen]="fullScreen">
|
||||||
<div class="page-header">
|
<div class="page-header">
|
||||||
<div class="flex flex-1">
|
<div class="flex flex-1">
|
||||||
<redaction-view-switch (switchView)="switchView($event)" [fileData]="fileData" [file]="file"></redaction-view-switch>
|
<redaction-view-switch (switchView)="switchView($event)" [file]="file"></redaction-view-switch>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex-1 actions-container">
|
<div class="flex-1 actions-container">
|
||||||
@ -76,7 +76,6 @@
|
|||||||
[canPerformActions]="canPerformAnnotationActions$ | async"
|
[canPerformActions]="canPerformAnnotationActions$ | async"
|
||||||
[class.hidden]="!ready"
|
[class.hidden]="!ready"
|
||||||
[dossier]="dossier"
|
[dossier]="dossier"
|
||||||
[fileData]="fileData?.fileData"
|
|
||||||
[file]="file"
|
[file]="file"
|
||||||
[shouldDeselectAnnotationsOnPageChange]="shouldDeselectAnnotationsOnPageChange"
|
[shouldDeselectAnnotationsOnPageChange]="shouldDeselectAnnotationsOnPageChange"
|
||||||
></redaction-pdf-viewer>
|
></redaction-pdf-viewer>
|
||||||
@ -102,7 +101,6 @@
|
|||||||
(deselectAnnotations)="deselectAnnotations($event)"
|
(deselectAnnotations)="deselectAnnotations($event)"
|
||||||
(selectAnnotations)="selectAnnotations($event)"
|
(selectAnnotations)="selectAnnotations($event)"
|
||||||
(selectPage)="selectPage($event)"
|
(selectPage)="selectPage($event)"
|
||||||
(toggleSkipped)="toggleSkipped($event)"
|
|
||||||
*ngIf="!file.excluded"
|
*ngIf="!file.excluded"
|
||||||
[(shouldDeselectAnnotationsOnPageChange)]="shouldDeselectAnnotationsOnPageChange"
|
[(shouldDeselectAnnotationsOnPageChange)]="shouldDeselectAnnotationsOnPageChange"
|
||||||
[activeViewerPage]="activeViewerPage"
|
[activeViewerPage]="activeViewerPage"
|
||||||
@ -110,9 +108,7 @@
|
|||||||
[annotations]="visibleAnnotations"
|
[annotations]="visibleAnnotations"
|
||||||
[dialogRef]="dialogRef"
|
[dialogRef]="dialogRef"
|
||||||
[file]="file"
|
[file]="file"
|
||||||
[hideSkipped]="hideSkipped"
|
|
||||||
[selectedAnnotations]="selectedAnnotations"
|
[selectedAnnotations]="selectedAnnotations"
|
||||||
[viewedPages]="fileData?.viewedPages"
|
|
||||||
[viewer]="activeViewer"
|
[viewer]="activeViewer"
|
||||||
></redaction-file-workload>
|
></redaction-file-workload>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -5,21 +5,21 @@ import { PdfViewerComponent } from './components/pdf-viewer/pdf-viewer.component
|
|||||||
import {
|
import {
|
||||||
AutoUnsubscribe,
|
AutoUnsubscribe,
|
||||||
CircleButtonTypes,
|
CircleButtonTypes,
|
||||||
|
CustomError,
|
||||||
Debounce,
|
Debounce,
|
||||||
|
ErrorService,
|
||||||
FilterService,
|
FilterService,
|
||||||
LoadingService,
|
LoadingService,
|
||||||
OnAttach,
|
OnAttach,
|
||||||
OnDetach,
|
OnDetach,
|
||||||
processFilters,
|
processFilters,
|
||||||
shareDistinctLast,
|
shareDistinctLast,
|
||||||
shareLast,
|
|
||||||
} from '@iqser/common-ui';
|
} from '@iqser/common-ui';
|
||||||
import { MatDialogRef, MatDialogState } from '@angular/material/dialog';
|
import { MatDialogRef, MatDialogState } from '@angular/material/dialog';
|
||||||
import { ManualRedactionEntryWrapper } from '@models/file/manual-redaction-entry.wrapper';
|
import { ManualRedactionEntryWrapper } from '@models/file/manual-redaction-entry.wrapper';
|
||||||
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
||||||
import { ManualAnnotationResponse } from '@models/file/manual-annotation-response';
|
import { ManualAnnotationResponse } from '@models/file/manual-annotation-response';
|
||||||
import { FileDataModel } from '@models/file/file-data.model';
|
import { AnnotationDrawService } from './services/annotation-draw.service';
|
||||||
import { AnnotationDrawService } from '../../services/annotation-draw.service';
|
|
||||||
import { AnnotationProcessingService } from '../../services/annotation-processing.service';
|
import { AnnotationProcessingService } from '../../services/annotation-processing.service';
|
||||||
import { Dossier, File, ViewMode } from '@red/domain';
|
import { Dossier, File, ViewMode } from '@red/domain';
|
||||||
import { PermissionsService } from '@services/permissions.service';
|
import { PermissionsService } from '@services/permissions.service';
|
||||||
@ -35,7 +35,7 @@ import { handleFilterDelta } from '@utils/filter-utils';
|
|||||||
import { FilesService } from '@services/entity-services/files.service';
|
import { FilesService } from '@services/entity-services/files.service';
|
||||||
import { DossiersService } from '@services/entity-services/dossiers.service';
|
import { DossiersService } from '@services/entity-services/dossiers.service';
|
||||||
import { FileManagementService } from '@services/entity-services/file-management.service';
|
import { FileManagementService } from '@services/entity-services/file-management.service';
|
||||||
import { filter, map, switchMap, tap } from 'rxjs/operators';
|
import { map, switchMap, tap } from 'rxjs/operators';
|
||||||
import { FilesMapService } from '@services/entity-services/files-map.service';
|
import { FilesMapService } from '@services/entity-services/files-map.service';
|
||||||
import { WatermarkService } from '@shared/services/watermark.service';
|
import { WatermarkService } from '@shared/services/watermark.service';
|
||||||
import { ExcludedPagesService } from './services/excluded-pages.service';
|
import { ExcludedPagesService } from './services/excluded-pages.service';
|
||||||
@ -43,23 +43,33 @@ import { ViewModeService } from './services/view-mode.service';
|
|||||||
import { MultiSelectService } from './services/multi-select.service';
|
import { MultiSelectService } from './services/multi-select.service';
|
||||||
import { DocumentInfoService } from './services/document-info.service';
|
import { DocumentInfoService } from './services/document-info.service';
|
||||||
import { ReanalysisService } from '../../../../services/reanalysis.service';
|
import { ReanalysisService } from '../../../../services/reanalysis.service';
|
||||||
|
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||||
|
import { CommentingService } from './services/commenting.service';
|
||||||
|
import { SkippedService } from './services/skipped.service';
|
||||||
|
import { AnnotationActionsService } from './services/annotation-actions.service';
|
||||||
|
import { FilePreviewStateService } from './services/file-preview-state.service';
|
||||||
|
import { FileDataModel } from '../../../../models/file/file-data.model';
|
||||||
import Annotation = Core.Annotations.Annotation;
|
import Annotation = Core.Annotations.Annotation;
|
||||||
import PDFNet = Core.PDFNet;
|
import PDFNet = Core.PDFNet;
|
||||||
|
|
||||||
const ALL_HOTKEY_ARRAY = ['Escape', 'F', 'f', 'ArrowUp', 'ArrowDown'];
|
const ALL_HOTKEY_ARRAY = ['Escape', 'F', 'f', 'ArrowUp', 'ArrowDown'];
|
||||||
|
|
||||||
function diff<T>(first: readonly T[], second: readonly T[]): T[] {
|
|
||||||
// symmetrical difference between two arrays
|
|
||||||
const a = new Set(first);
|
|
||||||
const b = new Set(second);
|
|
||||||
|
|
||||||
return [...first.filter(x => !b.has(x)), ...second.filter(x => !a.has(x))];
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
templateUrl: './file-preview-screen.component.html',
|
templateUrl: './file-preview-screen.component.html',
|
||||||
styleUrls: ['./file-preview-screen.component.scss'],
|
styleUrls: ['./file-preview-screen.component.scss'],
|
||||||
providers: [FilterService, ExcludedPagesService, ViewModeService, MultiSelectService, DocumentInfoService],
|
providers: [
|
||||||
|
FilterService,
|
||||||
|
ExcludedPagesService,
|
||||||
|
ViewModeService,
|
||||||
|
MultiSelectService,
|
||||||
|
DocumentInfoService,
|
||||||
|
CommentingService,
|
||||||
|
SkippedService,
|
||||||
|
AnnotationDrawService,
|
||||||
|
AnnotationActionsService,
|
||||||
|
FilePreviewStateService,
|
||||||
|
PdfViewerDataService,
|
||||||
|
],
|
||||||
})
|
})
|
||||||
export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnInit, OnDestroy, OnAttach, OnDetach {
|
export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnInit, OnDestroy, OnAttach, OnDetach {
|
||||||
readonly circleButtonTypes = CircleButtonTypes;
|
readonly circleButtonTypes = CircleButtonTypes;
|
||||||
@ -67,16 +77,13 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
|||||||
dialogRef: MatDialogRef<unknown>;
|
dialogRef: MatDialogRef<unknown>;
|
||||||
fullScreen = false;
|
fullScreen = false;
|
||||||
shouldDeselectAnnotationsOnPageChange = true;
|
shouldDeselectAnnotationsOnPageChange = true;
|
||||||
fileData: FileDataModel;
|
|
||||||
selectedAnnotations: AnnotationWrapper[] = [];
|
selectedAnnotations: AnnotationWrapper[] = [];
|
||||||
hideSkipped = false;
|
|
||||||
displayPdfViewer = false;
|
displayPdfViewer = false;
|
||||||
activeViewerPage: number = null;
|
activeViewerPage: number = null;
|
||||||
@ViewChild(PdfViewerComponent) readonly viewerComponent: PdfViewerComponent;
|
@ViewChild(PdfViewerComponent) readonly viewerComponent: PdfViewerComponent;
|
||||||
readonly dossierId: string;
|
readonly dossierId: string;
|
||||||
readonly canPerformAnnotationActions$: Observable<boolean>;
|
readonly canPerformAnnotationActions$: Observable<boolean>;
|
||||||
readonly dossier$: Observable<Dossier>;
|
readonly dossier$: Observable<Dossier>;
|
||||||
readonly file$: Observable<File>;
|
|
||||||
readonly fileId: string;
|
readonly fileId: string;
|
||||||
ready = false;
|
ready = false;
|
||||||
private _instance: WebViewerInstance;
|
private _instance: WebViewerInstance;
|
||||||
@ -91,6 +98,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
|||||||
constructor(
|
constructor(
|
||||||
readonly permissionsService: PermissionsService,
|
readonly permissionsService: PermissionsService,
|
||||||
readonly userPreferenceService: UserPreferenceService,
|
readonly userPreferenceService: UserPreferenceService,
|
||||||
|
private readonly _stateService: FilePreviewStateService,
|
||||||
private readonly _watermarkService: WatermarkService,
|
private readonly _watermarkService: WatermarkService,
|
||||||
private readonly _changeDetectorRef: ChangeDetectorRef,
|
private readonly _changeDetectorRef: ChangeDetectorRef,
|
||||||
private readonly _activatedRoute: ActivatedRoute,
|
private readonly _activatedRoute: ActivatedRoute,
|
||||||
@ -98,7 +106,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
|||||||
private readonly _router: Router,
|
private readonly _router: Router,
|
||||||
private readonly _annotationProcessingService: AnnotationProcessingService,
|
private readonly _annotationProcessingService: AnnotationProcessingService,
|
||||||
private readonly _annotationDrawService: AnnotationDrawService,
|
private readonly _annotationDrawService: AnnotationDrawService,
|
||||||
private readonly _fileDownloadService: PdfViewerDataService,
|
private readonly _pdfViewerDataService: PdfViewerDataService,
|
||||||
private readonly _filesService: FilesService,
|
private readonly _filesService: FilesService,
|
||||||
private readonly _ngZone: NgZone,
|
private readonly _ngZone: NgZone,
|
||||||
private readonly _fileManagementService: FileManagementService,
|
private readonly _fileManagementService: FileManagementService,
|
||||||
@ -108,6 +116,8 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
|||||||
private readonly _filesMapService: FilesMapService,
|
private readonly _filesMapService: FilesMapService,
|
||||||
private readonly _dossiersService: DossiersService,
|
private readonly _dossiersService: DossiersService,
|
||||||
private readonly _reanalysisService: ReanalysisService,
|
private readonly _reanalysisService: ReanalysisService,
|
||||||
|
private readonly _errorService: ErrorService,
|
||||||
|
private readonly _skippedService: SkippedService,
|
||||||
readonly excludedPagesService: ExcludedPagesService,
|
readonly excludedPagesService: ExcludedPagesService,
|
||||||
readonly viewModeService: ViewModeService,
|
readonly viewModeService: ViewModeService,
|
||||||
readonly multiSelectService: MultiSelectService,
|
readonly multiSelectService: MultiSelectService,
|
||||||
@ -117,12 +127,6 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
|||||||
this.dossierId = _activatedRoute.snapshot.paramMap.get('dossierId');
|
this.dossierId = _activatedRoute.snapshot.paramMap.get('dossierId');
|
||||||
this.dossier$ = _dossiersService.getEntityChanged$(this.dossierId);
|
this.dossier$ = _dossiersService.getEntityChanged$(this.dossierId);
|
||||||
this.fileId = _activatedRoute.snapshot.paramMap.get('fileId');
|
this.fileId = _activatedRoute.snapshot.paramMap.get('fileId');
|
||||||
this.file$ = _filesMapService.watch$(this.dossierId, this.fileId).pipe(
|
|
||||||
tap(async file => {
|
|
||||||
await this._reloadFile(file);
|
|
||||||
}),
|
|
||||||
shareLast(),
|
|
||||||
);
|
|
||||||
this.canPerformAnnotationActions$ = this._canPerformAnnotationActions$;
|
this.canPerformAnnotationActions$ = this._canPerformAnnotationActions$;
|
||||||
|
|
||||||
document.documentElement.addEventListener('fullscreenchange', () => {
|
document.documentElement.addEventListener('fullscreenchange', () => {
|
||||||
@ -144,8 +148,16 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
|||||||
return this._instance;
|
return this._instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get fileData(): FileDataModel {
|
||||||
|
return this._stateService.fileData;
|
||||||
|
}
|
||||||
|
|
||||||
private get _canPerformAnnotationActions$() {
|
private get _canPerformAnnotationActions$() {
|
||||||
return combineLatest([this.file$, this.viewModeService.viewMode$]).pipe(
|
return combineLatest([
|
||||||
|
this._stateService.fileData$.pipe(switchMap(fileData => fileData.file$)),
|
||||||
|
this.viewModeService.viewMode$,
|
||||||
|
this.viewModeService.compareMode$,
|
||||||
|
]).pipe(
|
||||||
map(([file, viewMode]) => this.permissionsService.canPerformAnnotationActions(file) && viewMode === 'STANDARD'),
|
map(([file, viewMode]) => this.permissionsService.canPerformAnnotationActions(file) && viewMode === 'STANDARD'),
|
||||||
shareDistinctLast(),
|
shareDistinctLast(),
|
||||||
);
|
);
|
||||||
@ -190,7 +202,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
|||||||
|
|
||||||
ngOnDetach(): void {
|
ngOnDetach(): void {
|
||||||
this.displayPdfViewer = false;
|
this.displayPdfViewer = false;
|
||||||
super.ngOnDestroy();
|
super.ngOnDetach();
|
||||||
this._changeDetectorRef.markForCheck();
|
this._changeDetectorRef.markForCheck();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -199,6 +211,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
|||||||
if (!file.canBeOpened) {
|
if (!file.canBeOpened) {
|
||||||
return this._router.navigate([this._dossiersService.find(this.dossierId)?.routerLink]);
|
return this._router.navigate([this._dossiersService.find(this.dossierId)?.routerLink]);
|
||||||
}
|
}
|
||||||
|
this.viewModeService.viewMode = 'STANDARD';
|
||||||
|
|
||||||
await this.ngOnInit();
|
await this.ngOnInit();
|
||||||
this._lastPage = previousRoute.queryParams.page;
|
this._lastPage = previousRoute.queryParams.page;
|
||||||
@ -250,11 +263,9 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
|||||||
});
|
});
|
||||||
console.log(`[REDACTION] Process time: ${new Date().getTime() - processStartTime} ms`);
|
console.log(`[REDACTION] Process time: ${new Date().getTime() - processStartTime} ms`);
|
||||||
console.log(`[REDACTION] Filter rebuild time: ${new Date().getTime() - startTime}`);
|
console.log(`[REDACTION] Filter rebuild time: ${new Date().getTime() - startTime}`);
|
||||||
console.log();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleAnnotationSelected(annotationIds: string[]) {
|
handleAnnotationSelected(annotationIds: string[]) {
|
||||||
// TODO: use includes() here
|
|
||||||
this.selectedAnnotations = annotationIds
|
this.selectedAnnotations = annotationIds
|
||||||
.map(id => this.visibleAnnotations.find(annotationWrapper => annotationWrapper.id === id))
|
.map(id => this.visibleAnnotations.find(annotationWrapper => annotationWrapper.id === id))
|
||||||
.filter(ann => ann !== undefined);
|
.filter(ann => ann !== undefined);
|
||||||
@ -268,7 +279,8 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
|||||||
@Debounce(10)
|
@Debounce(10)
|
||||||
selectAnnotations(annotations?: AnnotationWrapper[]) {
|
selectAnnotations(annotations?: AnnotationWrapper[]) {
|
||||||
if (annotations) {
|
if (annotations) {
|
||||||
this.viewerComponent?.utils?.selectAnnotations(annotations, this.multiSelectService.isActive);
|
const annotationsToSelect = this.multiSelectService.isActive ? [...this.selectedAnnotations, ...annotations] : annotations;
|
||||||
|
this.viewerComponent?.utils?.selectAnnotations(annotationsToSelect, this.multiSelectService.isActive);
|
||||||
} else {
|
} else {
|
||||||
this.viewerComponent?.utils?.deselectAllAnnotations();
|
this.viewerComponent?.utils?.deselectAllAnnotations();
|
||||||
}
|
}
|
||||||
@ -387,7 +399,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Go to initial page from query params
|
// Go to initial page from query params
|
||||||
const pageNumber = this._lastPage || this._activatedRoute.snapshot.queryParams.page;
|
const pageNumber: string = this._lastPage || this._activatedRoute.snapshot.queryParams.page;
|
||||||
if (pageNumber) {
|
if (pageNumber) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.selectPage(parseInt(pageNumber, 10));
|
this.selectPage(parseInt(pageNumber, 10));
|
||||||
@ -403,6 +415,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
|||||||
}
|
}
|
||||||
|
|
||||||
async annotationsChangedByReviewAction(annotation: AnnotationWrapper) {
|
async annotationsChangedByReviewAction(annotation: AnnotationWrapper) {
|
||||||
|
this.multiSelectService.deactivate();
|
||||||
await this._reloadAnnotationsForPage(annotation?.pageNumber || this.activeViewerPage);
|
await this._reloadAnnotationsForPage(annotation?.pageNumber || this.activeViewerPage);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -413,7 +426,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
|||||||
}
|
}
|
||||||
|
|
||||||
async switchView(viewMode: ViewMode) {
|
async switchView(viewMode: ViewMode) {
|
||||||
this.viewModeService.set(viewMode);
|
this.viewModeService.viewMode = viewMode;
|
||||||
await this.updateViewMode();
|
await this.updateViewMode();
|
||||||
this._scrollViews();
|
this._scrollViews();
|
||||||
}
|
}
|
||||||
@ -425,16 +438,6 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
|||||||
download(data, file.filename);
|
download(data, file.filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleSkipped($event) {
|
|
||||||
$event.stopPropagation();
|
|
||||||
$event.preventDefault();
|
|
||||||
this.hideSkipped = !this.hideSkipped;
|
|
||||||
|
|
||||||
this._handleIgnoreAnnotationsDrawing();
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _setActiveViewerPage() {
|
private _setActiveViewerPage() {
|
||||||
const currentPage = this._instance?.Core.documentViewer?.getCurrentPage();
|
const currentPage = this._instance?.Core.documentViewer?.getCurrentPage();
|
||||||
if (!currentPage) {
|
if (!currentPage) {
|
||||||
@ -464,17 +467,20 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async _reloadFile(file: File): Promise<void> {
|
private async _reloadFile(file: File): Promise<void> {
|
||||||
|
const previousFile = this.fileData?.file;
|
||||||
await this._loadFileData(file);
|
await this._loadFileData(file);
|
||||||
await this._stampPDF();
|
|
||||||
|
// file already loaded at least once
|
||||||
|
if (previousFile) {
|
||||||
|
// If it has been OCRd, we need to wait for it to load into the viewer
|
||||||
|
if (previousFile.lastOCRTime !== this.fileData?.file?.lastOCRTime) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _stampPDF() {
|
private async _stampPDF() {
|
||||||
if (!this._instance) {
|
if (!this._instance?.Core.documentViewer.getDocument()) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const document = this._instance.Core.documentViewer.getDocument();
|
|
||||||
if (!document) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -517,7 +523,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
|||||||
await stampPDFPage(
|
await stampPDFPage(
|
||||||
document,
|
document,
|
||||||
this._instance.Core.PDFNet,
|
this._instance.Core.PDFNet,
|
||||||
this._translateService.instant('file-preview.excluded-from-redaction'),
|
this._translateService.instant('file-preview.excluded-from-redaction') as string,
|
||||||
17,
|
17,
|
||||||
'courier',
|
'courier',
|
||||||
'TOP_LEFT',
|
'TOP_LEFT',
|
||||||
@ -528,19 +534,54 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async _fileUpdated(file: File): Promise<File> {
|
||||||
|
if (!this.fileData || file.lastProcessed === this.fileData.file.lastProcessed) {
|
||||||
|
await this._reloadFile(file);
|
||||||
|
} else {
|
||||||
|
// File reanalysed
|
||||||
|
const previousAnnotations = this.visibleAnnotations;
|
||||||
|
await this._loadFileData(file);
|
||||||
|
await this._reloadAnnotations(previousAnnotations);
|
||||||
|
}
|
||||||
|
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
|
||||||
private _subscribeToFileUpdates(): void {
|
private _subscribeToFileUpdates(): void {
|
||||||
this.addSubscription = timer(0, 5000)
|
this.addActiveScreenSubscription = this._filesMapService
|
||||||
|
.watch$(this.dossierId, this.fileId)
|
||||||
|
.pipe(switchMap(file => this._fileUpdated(file)))
|
||||||
|
.subscribe();
|
||||||
|
|
||||||
|
this.addActiveScreenSubscription = timer(0, 5000)
|
||||||
.pipe(switchMap(() => this._filesService.reload(this.dossierId, this.fileId)))
|
.pipe(switchMap(() => this._filesService.reload(this.dossierId, this.fileId)))
|
||||||
.subscribe();
|
.subscribe();
|
||||||
this.addSubscription = this._filesMapService.fileReanalysed$
|
|
||||||
.pipe(filter(file => file.fileId === this.fileId))
|
this.addActiveScreenSubscription = this._dossiersService
|
||||||
.subscribe(async file => {
|
.getEntityDeleted$(this.dossierId)
|
||||||
if (file.lastProcessed !== this.fileData?.file.lastProcessed) {
|
.pipe(tap(() => this._handleDeletedDossier()))
|
||||||
await this._loadFileData(file);
|
.subscribe();
|
||||||
await this._reloadAnnotations();
|
|
||||||
}
|
this.addActiveScreenSubscription = this._filesMapService
|
||||||
this._loadingService.stop();
|
.watchDeleted$(this.fileId)
|
||||||
});
|
.pipe(tap(() => this._handleDeletedFile()))
|
||||||
|
.subscribe();
|
||||||
|
|
||||||
|
this.addActiveScreenSubscription = this._skippedService.hideSkipped$
|
||||||
|
.pipe(tap(hideSkipped => this._handleIgnoreAnnotationsDrawing(hideSkipped)))
|
||||||
|
.subscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _handleDeletedDossier(): void {
|
||||||
|
this._errorService.set(
|
||||||
|
new CustomError(_('error.deleted-entity.file-dossier.label'), _('error.deleted-entity.file-dossier.action'), 'iqser:expand'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _handleDeletedFile(): void {
|
||||||
|
this._errorService.set(
|
||||||
|
new CustomError(_('error.deleted-entity.file.label'), _('error.deleted-entity.file.action'), 'iqser:expand'),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _loadFileData(file: File): Promise<void | boolean> {
|
private async _loadFileData(file: File): Promise<void | boolean> {
|
||||||
@ -548,13 +589,13 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
|||||||
return this._router.navigate([this._dossiersService.find(this.dossierId).routerLink]);
|
return this._router.navigate([this._dossiersService.find(this.dossierId).routerLink]);
|
||||||
}
|
}
|
||||||
|
|
||||||
const fileData = await this._fileDownloadService.loadDataFor(file, this.fileData).toPromise();
|
const fileData = await this._pdfViewerDataService.loadDataFor(file).toPromise();
|
||||||
|
|
||||||
if (file.isPending) {
|
if (file.isPending) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.fileData = fileData;
|
this._stateService.fileData = fileData;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Debounce(0)
|
@Debounce(0)
|
||||||
@ -563,13 +604,9 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
|||||||
this._workloadComponent?.scrollAnnotations();
|
this._workloadComponent?.scrollAnnotations();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _reloadAnnotations() {
|
private async _reloadAnnotations(previousAnnotations?: AnnotationWrapper[]) {
|
||||||
this.fileData.redactionLog = await this._fileDownloadService.loadRedactionLogFor(this.dossierId, this.fileId).toPromise();
|
this._deleteAnnotations();
|
||||||
this._instance.Core.annotationManager.deleteAnnotations(this._instance.Core.annotationManager.getAnnotationsList(), {
|
await this._cleanupAndRedrawAnnotations(previousAnnotations);
|
||||||
imported: true,
|
|
||||||
force: true,
|
|
||||||
});
|
|
||||||
await this._cleanupAndRedrawAnnotations();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _reloadAnnotationsForPage(page: number) {
|
private async _reloadAnnotationsForPage(page: number) {
|
||||||
@ -582,24 +619,42 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
|||||||
}
|
}
|
||||||
|
|
||||||
const currentPageAnnotations = this.visibleAnnotations.filter(a => a.pageNumber === page);
|
const currentPageAnnotations = this.visibleAnnotations.filter(a => a.pageNumber === page);
|
||||||
this.fileData.redactionLog = await this._fileDownloadService.loadRedactionLogFor(this.dossierId, this.fileId).toPromise();
|
this.fileData.redactionLog = await this._pdfViewerDataService.loadRedactionLogFor(this.dossierId, this.fileId).toPromise();
|
||||||
|
|
||||||
|
this._deleteAnnotations(currentPageAnnotations);
|
||||||
await this._cleanupAndRedrawAnnotations(currentPageAnnotations, annotation => annotation.pageNumber === page);
|
await this._cleanupAndRedrawAnnotations(currentPageAnnotations, annotation => annotation.pageNumber === page);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _deleteAnnotations(annotationsToDelete?: AnnotationWrapper[]) {
|
||||||
|
if (!this._instance?.Core.documentViewer.getDocument()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!annotationsToDelete) {
|
||||||
|
this._instance.Core.annotationManager.deleteAnnotations(this._instance.Core.annotationManager.getAnnotationsList(), {
|
||||||
|
imported: true,
|
||||||
|
force: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
annotationsToDelete?.forEach(annotation => {
|
||||||
|
this._findAndDeleteAnnotation(annotation.id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private async _cleanupAndRedrawAnnotations(
|
private async _cleanupAndRedrawAnnotations(
|
||||||
annotationsToDelete?: AnnotationWrapper[],
|
currentAnnotations?: AnnotationWrapper[],
|
||||||
newAnnotationsFilter?: (annotation: AnnotationWrapper) => boolean,
|
newAnnotationsFilter?: (annotation: AnnotationWrapper) => boolean,
|
||||||
) {
|
) {
|
||||||
|
if (!this._instance?.Core.documentViewer.getDocument()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.rebuildFilters();
|
this.rebuildFilters();
|
||||||
|
|
||||||
if (this.viewModeService.viewMode === 'STANDARD') {
|
if (this.viewModeService.viewMode === 'STANDARD') {
|
||||||
const startTime = new Date().getTime();
|
const startTime = new Date().getTime();
|
||||||
annotationsToDelete?.forEach(annotation => {
|
const newAnnotations = newAnnotationsFilter ? this.visibleAnnotations.filter(newAnnotationsFilter) : this.visibleAnnotations;
|
||||||
this._findAndDeleteAnnotation(annotation.id);
|
this._handleDeltaAnnotationFilters(currentAnnotations ?? [], newAnnotations);
|
||||||
});
|
|
||||||
const newAnnotations = newAnnotationsFilter ? this.allAnnotations.filter(newAnnotationsFilter) : this.allAnnotations;
|
|
||||||
this._handleDeltaAnnotationFilters(annotationsToDelete ?? [], newAnnotations);
|
|
||||||
await this._redrawAnnotations(newAnnotations);
|
await this._redrawAnnotations(newAnnotations);
|
||||||
console.log(
|
console.log(
|
||||||
`[REDACTION] Annotations redraw time: ${new Date().getTime() - startTime} ms for ${newAnnotations.length} annotations`,
|
`[REDACTION] Annotations redraw time: ${new Date().getTime() - startTime} ms for ${newAnnotations.length} annotations`,
|
||||||
@ -613,7 +668,6 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
|||||||
annotations,
|
annotations,
|
||||||
this.fileId,
|
this.fileId,
|
||||||
this.dossierId,
|
this.dossierId,
|
||||||
this.hideSkipped,
|
|
||||||
!!this.viewModeService.isCompare,
|
!!this.viewModeService.isCompare,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -630,6 +684,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
|||||||
|
|
||||||
const oldPageSpecificFilters = this._annotationProcessingService.getAnnotationFilter(currentPageAnnotations);
|
const oldPageSpecificFilters = this._annotationProcessingService.getAnnotationFilter(currentPageAnnotations);
|
||||||
const newPageSpecificFilters = this._annotationProcessingService.getAnnotationFilter(newPageAnnotations);
|
const newPageSpecificFilters = this._annotationProcessingService.getAnnotationFilter(newPageAnnotations);
|
||||||
|
|
||||||
handleFilterDelta(oldPageSpecificFilters, newPageSpecificFilters, primaryFilters);
|
handleFilterDelta(oldPageSpecificFilters, newPageSpecificFilters, primaryFilters);
|
||||||
this._filterService.addFilterGroup({
|
this._filterService.addFilterGroup({
|
||||||
...primaryFilterGroup,
|
...primaryFilterGroup,
|
||||||
@ -655,9 +710,13 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleIgnoreAnnotationsDrawing() {
|
private _handleIgnoreAnnotationsDrawing(hideSkipped: boolean): void {
|
||||||
const ignored = this._getAnnotations(a => a.getCustomData('skipped'));
|
const ignored = this._getAnnotations(a => a.getCustomData('skipped'));
|
||||||
return this.hideSkipped ? this._hide(ignored) : this._show(ignored);
|
if (hideSkipped) {
|
||||||
|
this._hide(ignored);
|
||||||
|
} else {
|
||||||
|
this._show(ignored);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _getAnnotations(predicate: (value) => unknown) {
|
private _getAnnotations(predicate: (value) => unknown) {
|
||||||
|
|||||||
@ -1,19 +1,19 @@
|
|||||||
import { EventEmitter, Inject, Injectable, NgZone } from '@angular/core';
|
import { EventEmitter, Inject, Injectable, NgZone } from '@angular/core';
|
||||||
import { PermissionsService } from '@services/permissions.service';
|
import { PermissionsService } from '@services/permissions.service';
|
||||||
import { ManualAnnotationService } from './manual-annotation.service';
|
import { ManualAnnotationService } from '../../../services/manual-annotation.service';
|
||||||
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { getFirstRelevantTextPart } from '@utils/functions';
|
import { getFirstRelevantTextPart } from '@utils/functions';
|
||||||
import { AnnotationPermissions } from '@models/file/annotation.permissions';
|
import { AnnotationPermissions } from '@models/file/annotation.permissions';
|
||||||
import { DossiersDialogService } from './dossiers-dialog.service';
|
import { DossiersDialogService } from '../../../services/dossiers-dialog.service';
|
||||||
import { BASE_HREF } from '../../../tokens';
|
import { BASE_HREF } from '../../../../../tokens';
|
||||||
import { UserService } from '@services/user.service';
|
import { UserService } from '@services/user.service';
|
||||||
import { Core, WebViewerInstance } from '@pdftron/webviewer';
|
import { Core, WebViewerInstance } from '@pdftron/webviewer';
|
||||||
import { Dossier, File, IAddRedactionRequest, ILegalBasisChangeRequest, IRectangle, IResizeRequest } from '@red/domain';
|
import { Dossier, File, IAddRedactionRequest, ILegalBasisChangeRequest, IRectangle, IResizeRequest } from '@red/domain';
|
||||||
import { toPosition } from '../utils/pdf-calculation.utils';
|
import { toPosition } from '../../../utils/pdf-calculation.utils';
|
||||||
import { AnnotationDrawService } from './annotation-draw.service';
|
import { AnnotationDrawService } from './annotation-draw.service';
|
||||||
import { translateQuads } from '../../../utils';
|
import { translateQuads } from '@utils/pdf-coordinates';
|
||||||
import { DossiersService } from '@services/entity-services/dossiers.service';
|
import { DossiersService } from '@services/entity-services/dossiers.service';
|
||||||
import Annotation = Core.Annotations.Annotation;
|
import Annotation = Core.Annotations.Annotation;
|
||||||
|
|
||||||
@ -5,10 +5,11 @@ import { AppStateService } from '@state/app-state.service';
|
|||||||
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
||||||
import { UserPreferenceService } from '@services/user-preference.service';
|
import { UserPreferenceService } from '@services/user-preference.service';
|
||||||
import { DossiersService } from '@services/entity-services/dossiers.service';
|
import { DossiersService } from '@services/entity-services/dossiers.service';
|
||||||
import { RedactionLogService } from './redaction-log.service';
|
import { RedactionLogService } from '../../../services/redaction-log.service';
|
||||||
import { environment } from '@environments/environment';
|
import { environment } from '@environments/environment';
|
||||||
|
|
||||||
import { IRectangle, ISectionGrid, ISectionRectangle } from '@red/domain';
|
import { IRectangle, ISectionGrid, ISectionRectangle } from '@red/domain';
|
||||||
|
import { SkippedService } from './skipped.service';
|
||||||
import Annotation = Core.Annotations.Annotation;
|
import Annotation = Core.Annotations.Annotation;
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@ -18,6 +19,7 @@ export class AnnotationDrawService {
|
|||||||
private readonly _dossiersService: DossiersService,
|
private readonly _dossiersService: DossiersService,
|
||||||
private readonly _redactionLogService: RedactionLogService,
|
private readonly _redactionLogService: RedactionLogService,
|
||||||
private readonly _userPreferenceService: UserPreferenceService,
|
private readonly _userPreferenceService: UserPreferenceService,
|
||||||
|
private readonly _skippedService: SkippedService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async drawAnnotations(
|
async drawAnnotations(
|
||||||
@ -25,7 +27,6 @@ export class AnnotationDrawService {
|
|||||||
annotationWrappers: AnnotationWrapper[],
|
annotationWrappers: AnnotationWrapper[],
|
||||||
fileId: string,
|
fileId: string,
|
||||||
dossierId: string,
|
dossierId: string,
|
||||||
hideSkipped = false,
|
|
||||||
compareMode = false,
|
compareMode = false,
|
||||||
) {
|
) {
|
||||||
if (!activeViewer) {
|
if (!activeViewer) {
|
||||||
@ -36,7 +37,7 @@ export class AnnotationDrawService {
|
|||||||
|
|
||||||
await pdfNet.runWithCleanup(
|
await pdfNet.runWithCleanup(
|
||||||
async () => {
|
async () => {
|
||||||
await this._drawAnnotations(activeViewer, annotationWrappers, fileId, dossierId, hideSkipped, compareMode);
|
await this._drawAnnotations(activeViewer, annotationWrappers, fileId, dossierId, compareMode);
|
||||||
},
|
},
|
||||||
environment.licenseKey ? atob(environment.licenseKey) : null,
|
environment.licenseKey ? atob(environment.licenseKey) : null,
|
||||||
);
|
);
|
||||||
@ -82,12 +83,9 @@ export class AnnotationDrawService {
|
|||||||
annotationWrappers: AnnotationWrapper[],
|
annotationWrappers: AnnotationWrapper[],
|
||||||
fileId: string,
|
fileId: string,
|
||||||
dossierId: string,
|
dossierId: string,
|
||||||
hideSkipped: boolean,
|
|
||||||
compareMode: boolean,
|
compareMode: boolean,
|
||||||
) {
|
) {
|
||||||
const annotations = annotationWrappers.map(annotation =>
|
const annotations = annotationWrappers.map(annotation => this._computeAnnotation(activeViewer, annotation, dossierId, compareMode));
|
||||||
this._computeAnnotation(activeViewer, annotation, dossierId, hideSkipped, compareMode),
|
|
||||||
);
|
|
||||||
const annotationManager = activeViewer.Core.annotationManager;
|
const annotationManager = activeViewer.Core.annotationManager;
|
||||||
annotationManager.addAnnotations(annotations, { imported: true });
|
annotationManager.addAnnotations(annotations, { imported: true });
|
||||||
await annotationManager.drawAnnotationsFromList(annotations);
|
await annotationManager.drawAnnotationsFromList(annotations);
|
||||||
@ -143,7 +141,6 @@ export class AnnotationDrawService {
|
|||||||
activeViewer: WebViewerInstance,
|
activeViewer: WebViewerInstance,
|
||||||
annotationWrapper: AnnotationWrapper,
|
annotationWrapper: AnnotationWrapper,
|
||||||
dossierId: string,
|
dossierId: string,
|
||||||
hideSkipped: boolean,
|
|
||||||
compareMode: boolean,
|
compareMode: boolean,
|
||||||
) {
|
) {
|
||||||
const pageNumber = compareMode ? annotationWrapper.pageNumber * 2 - 1 : annotationWrapper.pageNumber;
|
const pageNumber = compareMode ? annotationWrapper.pageNumber * 2 - 1 : annotationWrapper.pageNumber;
|
||||||
@ -177,7 +174,7 @@ export class AnnotationDrawService {
|
|||||||
|
|
||||||
annotation.Hidden =
|
annotation.Hidden =
|
||||||
annotationWrapper.isChangeLogRemoved ||
|
annotationWrapper.isChangeLogRemoved ||
|
||||||
(hideSkipped && annotationWrapper.isSkipped) ||
|
(this._skippedService.hideSkipped && annotationWrapper.isSkipped) ||
|
||||||
annotationWrapper.isOCR ||
|
annotationWrapper.isOCR ||
|
||||||
annotationWrapper.hidden;
|
annotationWrapper.hidden;
|
||||||
annotation.setCustomData('redact-manager', 'true');
|
annotation.setCustomData('redact-manager', 'true');
|
||||||
@ -0,0 +1,38 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { BehaviorSubject, Observable } from 'rxjs';
|
||||||
|
import { map, startWith } from 'rxjs/operators';
|
||||||
|
import { shareDistinctLast } from '@iqser/common-ui';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class CommentingService {
|
||||||
|
private _activeAnnotations = new BehaviorSubject<Set<string>>(new Set<string>());
|
||||||
|
|
||||||
|
isActive$(annotationId: string): Observable<boolean> {
|
||||||
|
return this._activeAnnotations.pipe(
|
||||||
|
map(annotations => annotations.has(annotationId)),
|
||||||
|
startWith(false),
|
||||||
|
shareDistinctLast(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
toggle(annotationId: string): void {
|
||||||
|
if (this._activeAnnotations.value.has(annotationId)) {
|
||||||
|
this._deactivate(annotationId);
|
||||||
|
} else {
|
||||||
|
this._activate(annotationId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _activate(annotationId: string): void {
|
||||||
|
const currentValue = this._activeAnnotations.value;
|
||||||
|
const newSet = new Set<string>(currentValue).add(annotationId);
|
||||||
|
this._activeAnnotations.next(newSet);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _deactivate(annotationId: string): void {
|
||||||
|
const currentValue = this._activeAnnotations.value;
|
||||||
|
const newSet = new Set<string>(currentValue);
|
||||||
|
newSet.delete(annotationId);
|
||||||
|
this._activeAnnotations.next(newSet);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { BehaviorSubject, Observable } from 'rxjs';
|
||||||
|
import { FileDataModel } from '@models/file/file-data.model';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class FilePreviewStateService {
|
||||||
|
readonly fileData$: Observable<FileDataModel>;
|
||||||
|
private readonly _fileData$ = new BehaviorSubject<FileDataModel>(undefined);
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.fileData$ = this._fileData$.asObservable();
|
||||||
|
}
|
||||||
|
|
||||||
|
get fileData(): FileDataModel {
|
||||||
|
return this._fileData$.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
set fileData(fileDataModel: FileDataModel) {
|
||||||
|
this._fileData$.next(fileDataModel);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { BehaviorSubject, Observable } from 'rxjs';
|
||||||
|
import { skip } from 'rxjs/operators';
|
||||||
|
import { shareDistinctLast } from '@iqser/common-ui';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class SkippedService {
|
||||||
|
readonly hideSkipped$: Observable<boolean>;
|
||||||
|
private readonly _hideSkipped$ = new BehaviorSubject(false);
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.hideSkipped$ = this._hideSkipped$.pipe(shareDistinctLast(), skip(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
get hideSkipped(): boolean {
|
||||||
|
return this._hideSkipped$.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleSkipped($event): void {
|
||||||
|
$event.stopPropagation();
|
||||||
|
$event.preventDefault();
|
||||||
|
this._hideSkipped$.next(!this.hideSkipped);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -5,10 +5,14 @@ import { ViewMode } from '@red/domain';
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class ViewModeService {
|
export class ViewModeService {
|
||||||
readonly viewMode$: Observable<ViewMode>;
|
readonly viewMode$: Observable<ViewMode>;
|
||||||
|
readonly compareMode$: Observable<Boolean>;
|
||||||
private readonly _viewMode$ = new BehaviorSubject<ViewMode>('STANDARD');
|
private readonly _viewMode$ = new BehaviorSubject<ViewMode>('STANDARD');
|
||||||
|
|
||||||
|
private readonly _compareMode$ = new BehaviorSubject<Boolean>(false);
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.viewMode$ = this._viewMode$.asObservable();
|
this.viewMode$ = this._viewMode$.asObservable();
|
||||||
|
this.compareMode$ = this._compareMode$.asObservable();
|
||||||
}
|
}
|
||||||
|
|
||||||
get viewMode() {
|
get viewMode() {
|
||||||
@ -23,15 +27,19 @@ export class ViewModeService {
|
|||||||
return this._viewMode$.value === 'DELTA';
|
return this._viewMode$.value === 'DELTA';
|
||||||
}
|
}
|
||||||
|
|
||||||
get isCompare() {
|
|
||||||
return this._viewMode$.value === 'COMPARE';
|
|
||||||
}
|
|
||||||
|
|
||||||
get isRedacted() {
|
get isRedacted() {
|
||||||
return this._viewMode$.value === 'REDACTED';
|
return this._viewMode$.value === 'REDACTED';
|
||||||
}
|
}
|
||||||
|
|
||||||
set(mode: ViewMode) {
|
set viewMode(mode: ViewMode) {
|
||||||
this._viewMode$.next(mode);
|
this._viewMode$.next(mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get isCompare() {
|
||||||
|
return this._compareMode$.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
set compareMode(compareMode: boolean) {
|
||||||
|
this._compareMode$.next(compareMode);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -30,6 +30,7 @@ export class DossiersDialogService extends DialogService<DialogType> {
|
|||||||
protected readonly _config: DialogConfig<DialogType> = {
|
protected readonly _config: DialogConfig<DialogType> = {
|
||||||
confirm: {
|
confirm: {
|
||||||
component: ConfirmationDialogComponent,
|
component: ConfirmationDialogComponent,
|
||||||
|
dialogConfig: { disableClose: false },
|
||||||
},
|
},
|
||||||
documentInfo: {
|
documentInfo: {
|
||||||
component: DocumentInfoDialogComponent,
|
component: DocumentInfoDialogComponent,
|
||||||
@ -45,6 +46,7 @@ export class DossiersDialogService extends DialogService<DialogType> {
|
|||||||
},
|
},
|
||||||
assignFile: {
|
assignFile: {
|
||||||
component: AssignReviewerApproverDialogComponent,
|
component: AssignReviewerApproverDialogComponent,
|
||||||
|
dialogConfig: { disableClose: false },
|
||||||
},
|
},
|
||||||
recategorizeImage: {
|
recategorizeImage: {
|
||||||
component: RecategorizeImageDialogComponent,
|
component: RecategorizeImageDialogComponent,
|
||||||
|
|||||||
@ -3,13 +3,14 @@ import { forkJoin, Observable, of } from 'rxjs';
|
|||||||
import { catchError, map, tap } from 'rxjs/operators';
|
import { catchError, map, tap } from 'rxjs/operators';
|
||||||
import { FileDataModel } from '@models/file/file-data.model';
|
import { FileDataModel } from '@models/file/file-data.model';
|
||||||
import { PermissionsService } from '@services/permissions.service';
|
import { PermissionsService } from '@services/permissions.service';
|
||||||
import { File } from '@red/domain';
|
import { File, IRedactionLog, IViewedPage } from '@red/domain';
|
||||||
import { FileManagementService } from '@services/entity-services/file-management.service';
|
import { FileManagementService } from '@services/entity-services/file-management.service';
|
||||||
import { RedactionLogService } from './redaction-log.service';
|
import { RedactionLogService } from './redaction-log.service';
|
||||||
import { ViewedPagesService } from '@services/entity-services/viewed-pages.service';
|
import { ViewedPagesService } from '@services/entity-services/viewed-pages.service';
|
||||||
import { AppStateService } from '../../../state/app-state.service';
|
import { AppStateService } from '../../../state/app-state.service';
|
||||||
import { DossiersService } from '../../../services/entity-services/dossiers.service';
|
import { DossiersService } from '../../../services/entity-services/dossiers.service';
|
||||||
import { UserPreferenceService } from '../../../services/user-preference.service';
|
import { UserPreferenceService } from '../../../services/user-preference.service';
|
||||||
|
import { FilePreviewStateService } from '../screens/file-preview-screen/services/file-preview-state.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class PdfViewerDataService {
|
export class PdfViewerDataService {
|
||||||
@ -21,6 +22,7 @@ export class PdfViewerDataService {
|
|||||||
private readonly _viewedPagesService: ViewedPagesService,
|
private readonly _viewedPagesService: ViewedPagesService,
|
||||||
private readonly _appStateService: AppStateService,
|
private readonly _appStateService: AppStateService,
|
||||||
private readonly _userPreferenceService: UserPreferenceService,
|
private readonly _userPreferenceService: UserPreferenceService,
|
||||||
|
private readonly _stateService: FilePreviewStateService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
loadRedactionLogFor(dossierId: string, fileId: string) {
|
loadRedactionLogFor(dossierId: string, fileId: string) {
|
||||||
@ -30,16 +32,17 @@ export class PdfViewerDataService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
loadDataFor(file: File, fileData?: FileDataModel): Observable<FileDataModel> {
|
loadDataFor(file: File): Observable<FileDataModel> {
|
||||||
const file$ = fileData?.file.cacheIdentifier === file.cacheIdentifier ? of(fileData.fileData) : this.downloadOriginalFile(file);
|
const fileData = this._stateService.fileData;
|
||||||
const reactionLog$ = this.loadRedactionLogFor(file.dossierId, file.fileId);
|
const blob$ = fileData?.file.cacheIdentifier === file.cacheIdentifier ? of(fileData.blob$.value) : this.downloadOriginalFile(file);
|
||||||
|
const redactionLog$ = this.loadRedactionLogFor(file.dossierId, file.fileId);
|
||||||
const viewedPages$ = this.getViewedPagesFor(file);
|
const viewedPages$ = this.getViewedPagesFor(file);
|
||||||
|
|
||||||
const dossier = this._dossiersService.find(file.dossierId);
|
const dossier = this._dossiersService.find(file.dossierId);
|
||||||
|
|
||||||
return forkJoin([file$, reactionLog$, viewedPages$]).pipe(
|
return forkJoin([blob$, redactionLog$, viewedPages$]).pipe(
|
||||||
map(
|
map(
|
||||||
data =>
|
(data: [blob: Blob, redactionLog: IRedactionLog, viewedPages: IViewedPage[]]) =>
|
||||||
new FileDataModel(
|
new FileDataModel(
|
||||||
file,
|
file,
|
||||||
...data,
|
...data,
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
ChangeDetectionStrategy,
|
ChangeDetectionStrategy,
|
||||||
|
ChangeDetectorRef,
|
||||||
Component,
|
Component,
|
||||||
EventEmitter,
|
EventEmitter,
|
||||||
HostBinding,
|
HostBinding,
|
||||||
@ -96,6 +97,7 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy,
|
|||||||
private readonly _userPreferenceService: UserPreferenceService,
|
private readonly _userPreferenceService: UserPreferenceService,
|
||||||
private readonly _reanalysisService: ReanalysisService,
|
private readonly _reanalysisService: ReanalysisService,
|
||||||
private readonly _router: Router,
|
private readonly _router: Router,
|
||||||
|
private readonly _changeRef: ChangeDetectorRef,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
@ -157,7 +159,7 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy,
|
|||||||
ariaExpanded: this._excludedPagesService?.shown$,
|
ariaExpanded: this._excludedPagesService?.shown$,
|
||||||
showDot: !!this.file.excludedPages?.length,
|
showDot: !!this.file.excludedPages?.length,
|
||||||
icon: 'red:exclude-pages',
|
icon: 'red:exclude-pages',
|
||||||
show: !!this._excludedPagesService,
|
show: !!this._excludedPagesService && !this.file.excluded,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: ActionTypes.circleBtn,
|
type: ActionTypes.circleBtn,
|
||||||
@ -224,7 +226,10 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy,
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this._dossiersService.getEntityChanged$(this.file.dossierId).pipe(tap(() => this._setup()));
|
this.addSubscription = this._dossiersService
|
||||||
|
.getEntityChanged$(this.file.dossierId)
|
||||||
|
.pipe(tap(() => this._setup()))
|
||||||
|
.subscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnChanges() {
|
ngOnChanges() {
|
||||||
@ -353,6 +358,8 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnDestroy,
|
|||||||
this.showReanalyseDossierOverview = this.canReanalyse && this.isDossierOverview && this.analysisForced;
|
this.showReanalyseDossierOverview = this.canReanalyse && this.isDossierOverview && this.analysisForced;
|
||||||
|
|
||||||
this.buttons = this._buttons;
|
this.buttons = this._buttons;
|
||||||
|
|
||||||
|
this._changeRef.markForCheck();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _setFileApproved() {
|
private async _setFileApproved() {
|
||||||
|
|||||||
@ -19,5 +19,6 @@ export const processingFileStatusTranslations: { [key in ProcessingFileStatus]:
|
|||||||
OCR_PROCESSING: _('file-status.ocr-processing'),
|
OCR_PROCESSING: _('file-status.ocr-processing'),
|
||||||
PROCESSING: _('file-status.processing'),
|
PROCESSING: _('file-status.processing'),
|
||||||
REPROCESS: _('file-status.reprocess'),
|
REPROCESS: _('file-status.reprocess'),
|
||||||
|
SURROUNDING_TEXT_PROCESSING: _('file-status.processing'),
|
||||||
UNPROCESSED: _('file-status.unprocessed'),
|
UNPROCESSED: _('file-status.unprocessed'),
|
||||||
};
|
};
|
||||||
|
|||||||
@ -111,9 +111,9 @@ export class PdfViewerUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
translateQuads(page: number, quads: any) {
|
translateQuad(page: number, quad: Core.Math.Quad) {
|
||||||
const rotation = this._documentViewer.getCompleteRotation(page);
|
const rotation = this._documentViewer.getCompleteRotation(page);
|
||||||
return translateQuads(page, rotation, quads);
|
return translateQuads(page, rotation, quad);
|
||||||
}
|
}
|
||||||
|
|
||||||
deselectAllAnnotations() {
|
deselectAllAnnotations() {
|
||||||
|
|||||||
@ -7,9 +7,9 @@ import {
|
|||||||
IPrepareDownloadRequest,
|
IPrepareDownloadRequest,
|
||||||
IRemoveDownloadRequest,
|
IRemoveDownloadRequest,
|
||||||
} from '@red/domain';
|
} from '@red/domain';
|
||||||
import { interval, Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { ConfigService } from '@services/config.service';
|
import { ConfigService } from '@services/config.service';
|
||||||
import { filter, map, switchMap, tap, withLatestFrom } from 'rxjs/operators';
|
import { map, switchMap, tap } from 'rxjs/operators';
|
||||||
import { KeycloakService } from 'keycloak-angular';
|
import { KeycloakService } from 'keycloak-angular';
|
||||||
import { UserService } from '@services/user.service';
|
import { UserService } from '@services/user.service';
|
||||||
import { EntitiesService, List, mapEach, RequiredParam, Validate } from '@iqser/common-ui';
|
import { EntitiesService, List, mapEach, RequiredParam, Validate } from '@iqser/common-ui';
|
||||||
@ -44,11 +44,12 @@ export class FileDownloadService extends EntitiesService<DownloadStatus, IDownlo
|
|||||||
}
|
}
|
||||||
|
|
||||||
async performDownload(status: DownloadStatus) {
|
async performDownload(status: DownloadStatus) {
|
||||||
const token = await this._keycloakService.getToken();
|
|
||||||
const anchor = document.createElement('a');
|
const anchor = document.createElement('a');
|
||||||
anchor.href = `${this._configService.values.API_URL}/async/download?access_token=${encodeURIComponent(
|
anchor.href = `${this._configService.values.API_URL}/async/download?storageId=${encodeURIComponent(status.storageId)}`;
|
||||||
token,
|
if (!this._configService.values.USE_SESSION_FOR_DOWNLOAD) {
|
||||||
)}&storageId=${encodeURIComponent(status.storageId)}`;
|
const token = await this._keycloakService.getToken();
|
||||||
|
anchor.href = anchor.href + `&access_token=${encodeURIComponent(token)}`;
|
||||||
|
}
|
||||||
anchor.download = status.filename;
|
anchor.download = status.filename;
|
||||||
anchor.target = '_blank';
|
anchor.target = '_blank';
|
||||||
|
|
||||||
|
|||||||
@ -41,7 +41,7 @@ export class FileUploadService extends GenericService<IFileUploadResult> impleme
|
|||||||
) {
|
) {
|
||||||
super(_injector, 'upload');
|
super(_injector, 'upload');
|
||||||
const fileFetch$ = this._fetchFiles$.pipe(
|
const fileFetch$ = this._fetchFiles$.pipe(
|
||||||
throttleTime(1500),
|
throttleTime(250),
|
||||||
switchMap(dossierId => this._filesService.loadAll(dossierId)),
|
switchMap(dossierId => this._filesService.loadAll(dossierId)),
|
||||||
);
|
);
|
||||||
this._subscriptions.add(fileFetch$.subscribe());
|
this._subscriptions.add(fileFetch$.subscribe());
|
||||||
|
|||||||
@ -2,16 +2,27 @@ import { Injectable, Injector } from '@angular/core';
|
|||||||
import { EntitiesService, List, mapEach, QueryParam, RequiredParam, shareLast, Toaster, Validate } from '@iqser/common-ui';
|
import { EntitiesService, List, mapEach, QueryParam, RequiredParam, shareLast, Toaster, Validate } from '@iqser/common-ui';
|
||||||
import { Dossier, IDossier, IDossierRequest } from '@red/domain';
|
import { Dossier, IDossier, IDossierRequest } from '@red/domain';
|
||||||
import { catchError, filter, map, mapTo, switchMap, tap } from 'rxjs/operators';
|
import { catchError, filter, map, mapTo, switchMap, tap } from 'rxjs/operators';
|
||||||
import { combineLatest, iif, Observable, of, throwError } from 'rxjs';
|
import { combineLatest, Observable, of, Subject, throwError, timer } from 'rxjs';
|
||||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||||
import { HttpErrorResponse, HttpStatusCode } from '@angular/common/http';
|
import { HttpErrorResponse, HttpStatusCode } from '@angular/common/http';
|
||||||
import { DossierStatsService } from '@services/entity-services/dossier-stats.service';
|
import { DossierStatsService } from '@services/entity-services/dossier-stats.service';
|
||||||
|
import { CHANGED_CHECK_INTERVAL } from '@utils/constants';
|
||||||
|
|
||||||
export interface IDossiersStats {
|
export interface IDossiersStats {
|
||||||
totalPeople: number;
|
totalPeople: number;
|
||||||
totalAnalyzedPages: number;
|
totalAnalyzedPages: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ChangesDetails {
|
||||||
|
dossierChanges: [
|
||||||
|
{
|
||||||
|
dossierChanges: boolean;
|
||||||
|
dossierId: string;
|
||||||
|
fileChanges: boolean;
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
const DOSSIER_EXISTS_MSG = _('add-dossier-dialog.errors.dossier-already-exists');
|
const DOSSIER_EXISTS_MSG = _('add-dossier-dialog.errors.dossier-already-exists');
|
||||||
const GENERIC_MGS = _('add-dossier-dialog.errors.generic');
|
const GENERIC_MGS = _('add-dossier-dialog.errors.generic');
|
||||||
|
|
||||||
@ -20,6 +31,7 @@ const GENERIC_MGS = _('add-dossier-dialog.errors.generic');
|
|||||||
})
|
})
|
||||||
export class DossiersService extends EntitiesService<Dossier, IDossier> {
|
export class DossiersService extends EntitiesService<Dossier, IDossier> {
|
||||||
readonly generalStats$ = this.all$.pipe(switchMap(entities => this._generalStats$(entities)));
|
readonly generalStats$ = this.all$.pipe(switchMap(entities => this._generalStats$(entities)));
|
||||||
|
readonly dossierFileChanges$ = new Subject<string>();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly _toaster: Toaster,
|
private readonly _toaster: Toaster,
|
||||||
@ -27,6 +39,13 @@ export class DossiersService extends EntitiesService<Dossier, IDossier> {
|
|||||||
private readonly _dossierStatsService: DossierStatsService,
|
private readonly _dossierStatsService: DossierStatsService,
|
||||||
) {
|
) {
|
||||||
super(_injector, Dossier, 'dossier');
|
super(_injector, Dossier, 'dossier');
|
||||||
|
|
||||||
|
timer(CHANGED_CHECK_INTERVAL, CHANGED_CHECK_INTERVAL)
|
||||||
|
.pipe(
|
||||||
|
switchMap(() => this.loadAllIfChanged()),
|
||||||
|
tap(changes => this._emitFileChanges(changes)),
|
||||||
|
)
|
||||||
|
.subscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
loadAll(): Observable<Dossier[]> {
|
loadAll(): Observable<Dossier[]> {
|
||||||
@ -39,8 +58,15 @@ export class DossiersService extends EntitiesService<Dossier, IDossier> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
loadAllIfChanged(): Observable<boolean> {
|
loadAllIfChanged(): Observable<ChangesDetails> {
|
||||||
return this.hasChanges$().pipe(switchMap(changed => iif(() => changed, this.loadAll()).pipe(mapTo(changed))));
|
return this.hasChangesDetails$().pipe(switchMap(changes => this.loadAll().pipe(mapTo(changes))));
|
||||||
|
}
|
||||||
|
|
||||||
|
hasChangesDetails$(): Observable<ChangesDetails> {
|
||||||
|
const body = { value: this._lastCheckedForChanges.get('root') ?? '0' };
|
||||||
|
return this._post<ChangesDetails>(body, `${this._defaultModelPath}/changes/details`).pipe(
|
||||||
|
filter(changes => changes.dossierChanges.length > 0),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Validate()
|
@Validate()
|
||||||
@ -82,6 +108,10 @@ export class DossiersService extends EntitiesService<Dossier, IDossier> {
|
|||||||
return super.delete(body, 'deleted-dossiers/hard-delete', body).toPromise();
|
return super.delete(body, 'deleted-dossiers/hard-delete', body).toPromise();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _emitFileChanges(changes: ChangesDetails): void {
|
||||||
|
changes.dossierChanges.filter(change => change.fileChanges).forEach(change => this.dossierFileChanges$.next(change.dossierId));
|
||||||
|
}
|
||||||
|
|
||||||
private _computeStats(entities: List<Dossier>): IDossiersStats {
|
private _computeStats(entities: List<Dossier>): IDossiersStats {
|
||||||
let totalAnalyzedPages = 0;
|
let totalAnalyzedPages = 0;
|
||||||
const totalPeople = new Set<string>();
|
const totalPeople = new Set<string>();
|
||||||
|
|||||||
@ -5,8 +5,8 @@ import { filter, startWith } from 'rxjs/operators';
|
|||||||
|
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class FilesMapService {
|
export class FilesMapService {
|
||||||
readonly fileReanalysed$ = new Subject<File>();
|
|
||||||
private readonly _entityChanged$ = new Subject<File>();
|
private readonly _entityChanged$ = new Subject<File>();
|
||||||
|
private readonly _entityDeleted$ = new Subject<File>();
|
||||||
private readonly _map = new Map<string, BehaviorSubject<File[]>>();
|
private readonly _map = new Map<string, BehaviorSubject<File[]>>();
|
||||||
|
|
||||||
get$(dossierId: string) {
|
get$(dossierId: string) {
|
||||||
@ -37,17 +37,13 @@ export class FilesMapService {
|
|||||||
return entities.forEach(entity => this._entityChanged$.next(entity));
|
return entities.forEach(entity => this._entityChanged$.next(entity));
|
||||||
}
|
}
|
||||||
|
|
||||||
const reanalysedEntities = [];
|
|
||||||
const changedEntities = [];
|
const changedEntities = [];
|
||||||
|
const deletedEntities = this.get(key).filter(oldEntity => !entities.find(newEntity => newEntity.id === oldEntity.id));
|
||||||
|
|
||||||
// Keep old object references for unchanged entities
|
// Keep old object references for unchanged entities
|
||||||
const newEntities = entities.map(newEntity => {
|
const newEntities = entities.map(newEntity => {
|
||||||
const oldEntity = this.get(key, newEntity.id);
|
const oldEntity = this.get(key, newEntity.id);
|
||||||
|
|
||||||
if (oldEntity?.lastProcessed !== newEntity.lastProcessed) {
|
|
||||||
reanalysedEntities.push(newEntity);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newEntity.isEqual(oldEntity)) {
|
if (newEntity.isEqual(oldEntity)) {
|
||||||
return oldEntity;
|
return oldEntity;
|
||||||
}
|
}
|
||||||
@ -60,13 +56,13 @@ export class FilesMapService {
|
|||||||
|
|
||||||
// Emit observables only after entities have been updated
|
// Emit observables only after entities have been updated
|
||||||
|
|
||||||
for (const file of reanalysedEntities) {
|
|
||||||
this.fileReanalysed$.next(file);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const file of changedEntities) {
|
for (const file of changedEntities) {
|
||||||
this._entityChanged$.next(file);
|
this._entityChanged$.next(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const file of deletedEntities) {
|
||||||
|
this._entityDeleted$.next(file);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
replace(entity: File) {
|
replace(entity: File) {
|
||||||
@ -83,4 +79,8 @@ export class FilesMapService {
|
|||||||
startWith(this.get(key, entityId)),
|
startWith(this.get(key, entityId)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
watchDeleted$(entityId: string): Observable<File> {
|
||||||
|
return this._entityDeleted$.pipe(filter(entity => entity.id === entityId));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { Injectable, Injector } from '@angular/core';
|
|||||||
import { GenericService, List, mapEach, QueryParam, RequiredParam, Validate } from '@iqser/common-ui';
|
import { GenericService, List, mapEach, QueryParam, RequiredParam, Validate } from '@iqser/common-ui';
|
||||||
import * as moment from 'moment';
|
import * as moment from 'moment';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { iif, Observable } from 'rxjs';
|
import { EMPTY, iif, Observable } from 'rxjs';
|
||||||
import { INotification, Notification, NotificationTypes } from '@red/domain';
|
import { INotification, Notification, NotificationTypes } from '@red/domain';
|
||||||
import { map, switchMap } from 'rxjs/operators';
|
import { map, switchMap } from 'rxjs/operators';
|
||||||
import { notificationsTranslations } from '../translations/notifications-translations';
|
import { notificationsTranslations } from '../translations/notifications-translations';
|
||||||
@ -40,7 +40,7 @@ export class NotificationsService extends GenericService<Notification> {
|
|||||||
|
|
||||||
@Validate()
|
@Validate()
|
||||||
getNotificationsIfChanged(@RequiredParam() includeSeen: boolean): Observable<Notification[]> {
|
getNotificationsIfChanged(@RequiredParam() includeSeen: boolean): Observable<Notification[]> {
|
||||||
return this.hasChanges$().pipe(switchMap(changed => iif(() => changed, this.getNotifications(includeSeen))));
|
return this.hasChanges$().pipe(switchMap(changed => iif(() => changed, this.getNotifications(includeSeen), EMPTY)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Validate()
|
@Validate()
|
||||||
@ -54,7 +54,7 @@ export class NotificationsService extends GenericService<Notification> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _new(notification: INotification) {
|
private _new(notification: INotification) {
|
||||||
const message = this._translate(notification, notificationsTranslations[notification.notificationType]);
|
const message = this._translate(notification, notificationsTranslations[notification.notificationType] as string);
|
||||||
const time = this._getTime(notification.creationDate);
|
const time = this._getTime(notification.creationDate);
|
||||||
return new Notification(notification, message, time);
|
return new Notification(notification, message, time);
|
||||||
}
|
}
|
||||||
@ -64,7 +64,7 @@ export class NotificationsService extends GenericService<Notification> {
|
|||||||
return moment(date).format('hh:mm A');
|
return moment(date).format('hh:mm A');
|
||||||
}
|
}
|
||||||
|
|
||||||
private _translate(notification: INotification, translation: string) {
|
private _translate(notification: INotification, translation: string): string {
|
||||||
const fileId = notification.target.fileId;
|
const fileId = notification.target.fileId;
|
||||||
const dossierId = notification.target.dossierId;
|
const dossierId = notification.target.dossierId;
|
||||||
const dossier = this._dossiersService.find(dossierId);
|
const dossier = this._dossiersService.find(dossierId);
|
||||||
|
|||||||
@ -97,7 +97,7 @@ export class PermissionsService {
|
|||||||
|
|
||||||
// TODO: Remove '?', after we make sure file is loaded before page
|
// TODO: Remove '?', after we make sure file is loaded before page
|
||||||
canPerformAnnotationActions(file: File): boolean {
|
canPerformAnnotationActions(file: File): boolean {
|
||||||
return (file?.isUnderReview || file?.isUnderApproval) && this.isFileAssignee(file);
|
return !file.excluded && (file?.isUnderReview || file?.isUnderApproval) && this.isFileAssignee(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
canUndoApproval(file: File | File[]): boolean {
|
canUndoApproval(file: File | File[]): boolean {
|
||||||
|
|||||||
@ -160,6 +160,16 @@ export class AppStateService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dictionaryData['dossier_redaction'] = new Dictionary(
|
||||||
|
{
|
||||||
|
hexColor: colors.manualRedactionColor || FALLBACK_COLOR,
|
||||||
|
type: 'dossier_redaction',
|
||||||
|
hint: false,
|
||||||
|
recommendation: false,
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
dictionaryData['declined-suggestion'] = new Dictionary(
|
dictionaryData['declined-suggestion'] = new Dictionary(
|
||||||
{
|
{
|
||||||
hexColor: colors.notRedacted || FALLBACK_COLOR,
|
hexColor: colors.notRedacted || FALLBACK_COLOR,
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
export const CHANGED_CHECK_INTERVAL = 3000;
|
export const CHANGED_CHECK_INTERVAL = 5000;
|
||||||
|
|||||||
@ -98,9 +98,9 @@ export function removeBraces(str: any): string {
|
|||||||
return str.replace(/[{}]/g, '');
|
return str.replace(/[{}]/g, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function toKebabCase(str: string): string {
|
export function toSnakeCase(str: string): string {
|
||||||
return str
|
return str
|
||||||
.replace(/([a-z])([A-Z])/g, '$1-$2')
|
.replace(/([a-z])([A-Z])/g, '$1-$2')
|
||||||
.replace(/[\s_]+/g, '-')
|
.replace(/[\s_]+/g, '_')
|
||||||
.toLowerCase();
|
.toLowerCase();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"ADMIN_CONTACT_NAME": null,
|
"ADMIN_CONTACT_NAME": null,
|
||||||
"ADMIN_CONTACT_URL": null,
|
"ADMIN_CONTACT_URL": null,
|
||||||
"API_URL": "https://dev-08.iqser.cloud/redaction-gateway-v1",
|
"API_URL": "https://aks-staging.iqser.cloud/redaction-gateway-v1",
|
||||||
"APP_NAME": "RedactManager",
|
"APP_NAME": "RedactManager",
|
||||||
"AUTO_READ_TIME": 1.5,
|
"AUTO_READ_TIME": 3,
|
||||||
"BACKEND_APP_VERSION": "4.4.40",
|
"BACKEND_APP_VERSION": "4.4.40",
|
||||||
"DELETE_RETENTION_HOURS": 96,
|
"DELETE_RETENTION_HOURS": 96,
|
||||||
"EULA_URL": "EULA_URL",
|
"EULA_URL": "EULA_URL",
|
||||||
@ -17,7 +17,8 @@
|
|||||||
"MAX_RETRIES_ON_SERVER_ERROR": 3,
|
"MAX_RETRIES_ON_SERVER_ERROR": 3,
|
||||||
"OAUTH_CLIENT_ID": "redaction",
|
"OAUTH_CLIENT_ID": "redaction",
|
||||||
"OAUTH_IDP_HINT": null,
|
"OAUTH_IDP_HINT": null,
|
||||||
"OAUTH_URL": "https://dev-08.iqser.cloud/auth/realms/redaction",
|
"OAUTH_URL": "https://aks-staging.iqser.cloud/auth/realms/redaction",
|
||||||
"RECENT_PERIOD_IN_HOURS": 24,
|
"RECENT_PERIOD_IN_HOURS": 24,
|
||||||
"SELECTION_MODE": "structural"
|
"SELECTION_MODE": "structural",
|
||||||
|
"USE_SESSION_FOR_DOWNLOAD": false
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -448,12 +448,25 @@
|
|||||||
"question": "Are you sure you want to delete {filesCount, plural, one{this document} other{these documents}}?",
|
"question": "Are you sure you want to delete {filesCount, plural, one{this document} other{these documents}}?",
|
||||||
"title": "Delete {filesCount, plural, one{{fileName}} other{Selected Documents}}"
|
"title": "Delete {filesCount, plural, one{{fileName}} other{Selected Documents}}"
|
||||||
},
|
},
|
||||||
|
"unsaved-changes": {
|
||||||
|
"confirmation-text": "Save and Leave",
|
||||||
|
"details": "If you leave the tab without saving, all the unsaved changes will be lost.",
|
||||||
|
"discard-changes-text": "DISCARD CHANGES",
|
||||||
|
"question": "Are you sure you want to leave the tab? You have unsaved changes.",
|
||||||
|
"title": "You have unsaved changes"
|
||||||
|
},
|
||||||
"upload-report-template": {
|
"upload-report-template": {
|
||||||
"alternate-confirmation-text": "Upload as multi-file report",
|
"alternate-confirmation-text": "Upload as multi-file report",
|
||||||
"confirmation-text": "Upload as single-file report",
|
"confirmation-text": "Upload as single-file report",
|
||||||
"deny-text": "Cancel Upload",
|
"deny-text": "Cancel Upload",
|
||||||
"question": "Please choose if <b>{fileName}</b> is a single or multi-file report template",
|
"question": "Please choose if <b>{fileName}</b> is a single or multi-file report template",
|
||||||
"title": "Report Template Upload"
|
"title": "Report Template Upload"
|
||||||
|
},
|
||||||
|
"report-template-same-name": {
|
||||||
|
"confirmation-text": "Yes. Continue upload",
|
||||||
|
"deny-text": "No. Cancel Upload",
|
||||||
|
"question": "There is already a Report Template with the name: <b>{fileName}</b>. Do you wish to continue?",
|
||||||
|
"title": "Report Template Upload"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"content": "Reason",
|
"content": "Reason",
|
||||||
@ -943,10 +956,23 @@
|
|||||||
"members": "Members",
|
"members": "Members",
|
||||||
"team-members": "Team Members"
|
"team-members": "Team Members"
|
||||||
},
|
},
|
||||||
"side-nav-title": "Configurations",
|
"side-nav-title": "Configurations"
|
||||||
"unsaved-changes": "You have unsaved changes. Save or revert before changing the tab."
|
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
|
"deleted-entity": {
|
||||||
|
"dossier": {
|
||||||
|
"action": "Back to overview",
|
||||||
|
"label": "This dossier has been deleted!"
|
||||||
|
},
|
||||||
|
"file-dossier": {
|
||||||
|
"action": "Back to overview",
|
||||||
|
"label": "The dossier of this file has been deleted!"
|
||||||
|
},
|
||||||
|
"file": {
|
||||||
|
"action": "Back to dossier",
|
||||||
|
"label": "This file has been deleted!"
|
||||||
|
}
|
||||||
|
},
|
||||||
"http": {
|
"http": {
|
||||||
"generic": "Action failed with code {status}"
|
"generic": "Action failed with code {status}"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg height="90px" version="1.1" viewBox="-10 -10 110 110" width="90px"
|
||||||
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g fill="none" fill-rule="evenodd" id="Symbols" stroke="none" stroke-width="1">
|
||||||
|
<g id="disable-tooltip">
|
||||||
|
<rect height="100" id="Rectangle" width="100" x="0" y="0"></rect>
|
||||||
|
<path
|
||||||
|
d="M50,0 C75,0 95,20 95,45 C95,45.3345638 94.9964181,45.6682321 94.9892904,46.0009689 L84.9863576,46.0005557 C84.9954332,45.6682544 85,45.3347165 85,45 C85,25.5 69.5,10 50,10 C30.5,10 15,25.5 15,45 C15,53.3152174 17.870983,61.1519376 23.1448539,67.5739706 L23.5,68 L25,69.5 L25,86 L43,79.5 L43.2,79.5 L43.2,89.79 L15,100 L15,73 C8.5,65 5,55.5 5,45 C5,20 25,0 50,0 Z M87.5553391,55 L94.6264069,62.0710678 L80.484,76.213 L94.6264069,90.3553391 L87.5553391,97.4264069 L73.413,83.284 L59.2710678,97.4264069 L52.2,90.3553391 L66.342,76.213 L52.2,62.0710678 L59.2710678,55 L73.413,69.142 L87.5553391,55 Z"
|
||||||
|
fill="#868E96" fill-rule="nonzero" id="Combined-Shape"></path>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 630 B After Width: | Height: | Size: 630 B |
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user