Merge branch 'master' into VM/NotificationsPreferences

This commit is contained in:
Valentin 2021-09-28 18:38:22 +03:00
commit 3bbba77f45
159 changed files with 2635 additions and 2140 deletions

View File

@ -64,7 +64,7 @@ export class BaseScreenComponent {
text: this._translateService.instant('search.this-dossier'),
icon: 'red:enter',
hide: (): boolean => !this.appStateService.activeDossier,
action: (query): void => this._search(query, this.appStateService.activeDossier.dossierId)
action: (query): void => this._search(query, this.appStateService.activeDossier.id)
},
{
text: this._translateService.instant('search.entire-platform'),

View File

@ -21,7 +21,6 @@ export class DownloadsListScreenComponent extends ListingComponent<DownloadStatu
@ViewChild('sizeTemplate', { static: true }) sizeTemplate: TemplateRef<never>;
@ViewChild('creationDateTemplate', { static: true }) creationDateTemplate: TemplateRef<never>;
@ViewChild('statusTemplate', { static: true }) statusTemplate: TemplateRef<never>;
protected readonly _primaryKey = 'storageId';
constructor(
protected readonly _injector: Injector,

View File

@ -1,38 +0,0 @@
import { AuditModel } from '@redaction/red-ui-http';
import { Listable } from '@iqser/common-ui';
export class AuditModelWrapper implements Listable {
constructor(public auditModel: AuditModel) {}
get category(): string {
return this.auditModel.category;
}
get details(): any {
return this.auditModel.details;
}
get message(): string {
return this.auditModel.message;
}
get recordId(): string {
return this.auditModel.recordId;
}
get recordDate(): string {
return this.auditModel.recordDate;
}
get objectId(): string {
return this.auditModel.objectId;
}
get userId(): string {
return this.auditModel.userId;
}
get id() {
return this.auditModel.recordDate;
}
}

View File

@ -0,0 +1,30 @@
import { IAudit } from '@redaction/red-ui-http';
import { IListable } from '@iqser/common-ui';
export class Audit implements IAudit, IListable {
readonly category?: string;
readonly details?: unknown;
readonly message?: string;
readonly objectId?: string;
readonly recordDate?: string;
readonly recordId?: string;
readonly userId?: string;
constructor(audit: IAudit) {
this.category = audit.category;
this.details = audit.details;
this.message = audit.message;
this.objectId = audit.objectId;
this.recordDate = audit.recordDate;
this.recordId = audit.recordId;
this.userId = audit.userId;
}
get id() {
return this.recordDate;
}
get searchKey(): string {
return this.recordDate;
}
}

View File

@ -0,0 +1,36 @@
import { IDictionary } from '@redaction/red-ui-http';
import { IListable, List } from '@iqser/common-ui';
export class Dictionary implements IDictionary, IListable {
readonly addToDictionaryAction: boolean;
readonly caseInsensitive: boolean;
readonly description?: string;
readonly dossierTemplateId?: string;
readonly entries?: List;
readonly hexColor?: string;
readonly hint: boolean;
readonly label?: string;
readonly rank?: number;
readonly recommendation: boolean;
constructor(dictionary: IDictionary) {
this.addToDictionaryAction = !!dictionary.addToDictionaryAction;
this.caseInsensitive = !!dictionary.caseInsensitive;
this.description = dictionary.description;
this.dossierTemplateId = dictionary.dossierTemplateId;
this.entries = dictionary.entries;
this.hexColor = dictionary.hexColor;
this.hint = !!dictionary.hint;
this.label = dictionary.label;
this.rank = dictionary.rank;
this.recommendation = !!dictionary.recommendation;
}
get id(): string {
return this.label;
}
get searchKey(): string {
return this.label;
}
}

View File

@ -1,3 +1,3 @@
import { DossierAttributeConfig } from '@redaction/red-ui-http';
import { IDossierAttributeConfig } from '@redaction/red-ui-http';
export type DossierAttributeWithValue = DossierAttributeConfig & { value: any };
export type DossierAttributeWithValue = IDossierAttributeConfig & { value: any };

View File

@ -1,6 +1,6 @@
import { UserWrapper } from '@services/user.service';
import { AnnotationWrapper } from './annotation.wrapper';
import { isArray } from 'rxjs/internal-compatibility';
import { User } from '@models/user';
export class AnnotationPermissions {
canUndo = true;
@ -14,7 +14,7 @@ export class AnnotationPermissions {
canChangeLegalBasis = true;
canRecategorizeImage = true;
static forUser(isApprover: boolean, user: UserWrapper, annotations: AnnotationWrapper | AnnotationWrapper[]) {
static forUser(isApprover: boolean, user: User, annotations: AnnotationWrapper | AnnotationWrapper[]) {
if (!isArray(annotations)) {
annotations = [annotations];
}

View File

@ -1,57 +0,0 @@
import { DossierTemplateModel, FileAttributesConfig } from '@redaction/red-ui-http';
import { Listable } from '@iqser/common-ui';
export class DossierTemplateModelWrapper implements Listable {
dictionariesCount = 0;
totalDictionaryEntries = 0;
constructor(public dossierTemplateModel: DossierTemplateModel, public fileAttributesConfig: FileAttributesConfig) {}
get id() {
return this.dossierTemplateModel.dossierTemplateId;
}
get createdBy() {
return this.dossierTemplateModel.createdBy;
}
get dateAdded() {
return this.dossierTemplateModel.dateAdded;
}
get dateModified() {
return this.dossierTemplateModel.dateModified;
}
get description() {
return this.dossierTemplateModel.description;
}
get dossierTemplateId() {
return this.dossierTemplateModel.dossierTemplateId;
}
get downloadFileTypes() {
return this.dossierTemplateModel.downloadFileTypes;
}
get modifiedBy() {
return this.dossierTemplateModel.modifiedBy;
}
get name() {
return this.dossierTemplateModel.name;
}
get reportTemplateIds() {
return this.dossierTemplateModel.reportTemplateIds;
}
get validFrom() {
return this.dossierTemplateModel.validFrom;
}
get validTo() {
return this.dossierTemplateModel.validTo;
}
}

View File

@ -0,0 +1,44 @@
import { DownloadFileType, FileAttributesConfig, IDossierTemplate, List } from '@redaction/red-ui-http';
import { IListable } from '@iqser/common-ui';
export class DossierTemplate implements IDossierTemplate, IListable {
readonly createdBy?: string;
readonly dateAdded?: string;
readonly dateModified?: string;
readonly description?: string;
readonly dossierTemplateId?: string;
readonly downloadFileTypes?: List<DownloadFileType>;
readonly modifiedBy?: string;
readonly name?: string;
readonly reportTemplateIds?: List;
readonly validFrom?: string;
readonly validTo?: string;
dictionariesCount = 0;
totalDictionaryEntries = 0;
constructor(dossierTemplate: IDossierTemplate, public fileAttributesConfig: FileAttributesConfig) {
this.createdBy = dossierTemplate.createdBy;
this.dateAdded = dossierTemplate.dateAdded;
this.dateModified = dossierTemplate.dateModified;
this.description = dossierTemplate.description;
this.dossierTemplateId = dossierTemplate.dossierTemplateId;
this.downloadFileTypes = dossierTemplate.downloadFileTypes;
this.modifiedBy = dossierTemplate.modifiedBy;
this.name = dossierTemplate.name;
this.reportTemplateIds = dossierTemplate.reportTemplateIds;
this.validFrom = dossierTemplate.validFrom;
this.validTo = dossierTemplate.validTo;
}
get id(): string {
return this.dossierTemplateId;
}
get searchKey(): string {
return this.name;
}
get routerLink(): string {
return `/main/admin/dossier-templates/${this.dossierTemplateId}/dictionaries`;
}
}

View File

@ -0,0 +1,30 @@
import { FileAttributeConfigType, IFileAttributeConfig } from '@redaction/red-ui-http';
import { IListable } from '@iqser/common-ui';
export class FileAttributeConfig implements IFileAttributeConfig, IListable {
readonly id: string;
readonly csvColumnHeader?: string;
readonly editable?: boolean;
readonly label?: string;
readonly placeholder?: string;
readonly primaryAttribute?: boolean;
readonly displayedInFileList?: boolean;
readonly filterable?: boolean;
readonly type?: FileAttributeConfigType;
constructor(fileAttributeConfig: IFileAttributeConfig) {
this.id = fileAttributeConfig.id;
this.csvColumnHeader = fileAttributeConfig.csvColumnHeader;
this.editable = !!fileAttributeConfig.editable;
this.label = fileAttributeConfig.label;
this.placeholder = fileAttributeConfig.placeholder;
this.primaryAttribute = fileAttributeConfig.primaryAttribute;
this.displayedInFileList = fileAttributeConfig.displayedInFileList;
this.filterable = !!fileAttributeConfig.filterable;
this.type = fileAttributeConfig.type;
}
get searchKey(): string {
return this.label;
}
}

View File

@ -1,10 +1,10 @@
import { RedactionChangeLog, RedactionLog, ViewedPages } from '@redaction/red-ui-http';
import { FileStatusWrapper } from './file-status.wrapper';
import { UserWrapper } from '@services/user.service';
import { File } from './file';
import { AnnotationWrapper } from './annotation.wrapper';
import { RedactionLogEntryWrapper } from './redaction-log-entry.wrapper';
import { ViewMode } from './view-mode';
import { TypeValueWrapper } from './type-value.wrapper';
import { TypeValue } from './type-value';
import { User } from '@models/user';
export class AnnotationData {
visibleAnnotations: AnnotationWrapper[];
@ -13,7 +13,7 @@ export class AnnotationData {
export class FileDataModel {
constructor(
public fileStatus: FileStatusWrapper,
public file: File,
public fileData: Blob,
public redactionLog: RedactionLog,
public redactionChangeLog: RedactionChangeLog,
@ -21,15 +21,15 @@ export class FileDataModel {
) {}
getAnnotations(
dictionaryData: { [p: string]: TypeValueWrapper },
currentUser: UserWrapper,
dictionaryData: { [p: string]: TypeValue },
currentUser: User,
viewMode: ViewMode,
areDevFeaturesEnabled: boolean
): AnnotationData {
const entries: RedactionLogEntryWrapper[] = this._convertData();
let allAnnotations = entries
.map(entry => AnnotationWrapper.fromData(entry))
.filter(ann => !this.fileStatus.excludedPages.includes(ann.pageNumber));
.filter(ann => !this.file.excludedPages.includes(ann.pageNumber));
if (!areDevFeaturesEnabled) {
allAnnotations = allAnnotations.filter(annotation => !annotation.isFalsePositive);

View File

@ -1,103 +0,0 @@
import { Listable } from '@iqser/common-ui';
import { FileAttributesConfig, FileStatus } from '@redaction/red-ui-http';
import { StatusSorter } from '@utils/sorters/status-sorter';
const processingStatuses = [
FileStatus.StatusEnum.REPROCESS,
FileStatus.StatusEnum.FULLREPROCESS,
FileStatus.StatusEnum.OCRPROCESSING,
FileStatus.StatusEnum.INDEXING,
FileStatus.StatusEnum.PROCESSING
] as const;
export class FileStatusWrapper implements FileStatus, Listable {
readonly added = this.fileStatus.added;
readonly allManualRedactionsApplied = this.fileStatus.allManualRedactionsApplied;
readonly analysisDuration = this.fileStatus.analysisDuration;
readonly analysisRequired = this.fileStatus.analysisRequired && !this.fileStatus.excluded;
readonly approvalDate = this.fileStatus.approvalDate;
readonly currentReviewer = this.fileStatus.currentReviewer;
readonly dictionaryVersion = this.fileStatus.dictionaryVersion;
readonly dossierDictionaryVersion = this.fileStatus.dossierDictionaryVersion;
readonly dossierId = this.fileStatus.dossierId;
readonly excluded = this.fileStatus.excluded;
readonly fileAttributes = this.fileStatus.fileAttributes;
readonly fileId = this.fileStatus.fileId;
readonly filename = this.fileStatus.filename;
readonly hasAnnotationComments = this.fileStatus.hasAnnotationComments;
readonly hasHints = this.fileStatus.hasHints;
readonly hasImages = this.fileStatus.hasImages;
readonly hasRedactions = this.fileStatus.hasRedactions;
readonly hasUpdates = this.fileStatus.hasUpdates;
readonly lastOCRTime = this.fileStatus.lastOCRTime;
readonly lastProcessed = this.fileStatus.lastProcessed;
readonly lastReviewer = this.fileStatus.lastReviewer;
readonly lastUpdated = this.fileStatus.lastUpdated;
readonly lastUploaded = this.fileStatus.lastUploaded;
readonly legalBasisVersion = this.fileStatus.legalBasisVersion;
readonly numberOfAnalyses = this.fileStatus.numberOfAnalyses;
readonly numberOfPages = this.fileStatus.numberOfPages;
readonly rulesVersion = this.fileStatus.rulesVersion;
readonly status = this._status;
readonly uploader = this.fileStatus.uploader;
readonly excludedPages = this.fileStatus.excludedPages;
readonly hasSuggestions = this.fileStatus.hasSuggestions;
readonly dossierTemplateId = this.fileStatus.dossierTemplateId;
primaryAttribute: string;
lastOpened: boolean;
constructor(readonly fileStatus: FileStatus, public reviewerName: string, fileAttributesConfig?: FileAttributesConfig) {
if (fileAttributesConfig) {
const primary = fileAttributesConfig.fileAttributeConfigs?.find(c => c.primaryAttribute);
if (primary && fileStatus.fileAttributes?.attributeIdToValue) {
this.primaryAttribute = fileStatus.fileAttributes?.attributeIdToValue[primary.id];
}
if (!this.primaryAttribute) {
// Fallback here
this.primaryAttribute = '-';
}
}
if (!this.fileAttributes || !this.fileAttributes.attributeIdToValue) {
this.fileAttributes = { attributeIdToValue: {} };
}
}
readonly excludedPagesCount = this.excludedPages?.length ?? 0;
readonly statusSort = StatusSorter[this.status];
readonly pages = this._pages;
readonly cacheIdentifier = btoa(this.lastUploaded + this.lastOCRTime);
readonly hintsOnly = this.hasHints && !this.hasRedactions;
readonly hasNone = !this.hasRedactions && !this.hasHints && !this.hasSuggestions;
readonly isUnassigned = !this.currentReviewer;
readonly isError = this.status === FileStatus.StatusEnum.ERROR;
readonly isProcessing = processingStatuses.includes(this.status);
readonly isApproved = this.status === FileStatus.StatusEnum.APPROVED;
readonly isPending = this.status === FileStatus.StatusEnum.UNPROCESSED;
readonly isUnderReview = this.status === FileStatus.StatusEnum.UNDERREVIEW;
readonly isUnderApproval = this.status === FileStatus.StatusEnum.UNDERAPPROVAL;
readonly canBeApproved = !this.analysisRequired && !this.hasSuggestions;
readonly canBeOpened = !this.isError && !this.isPending;
readonly isWorkable = !this.isProcessing && this.canBeOpened;
readonly canBeOCRed = !this.excluded && !this.lastOCRTime && ['UNASSIGNED', 'UNDER_REVIEW', 'UNDER_APPROVAL'].includes(this.status);
get id() {
return this.fileId;
}
private get _pages() {
if (this.fileStatus.status === 'ERROR') {
return -1;
}
return this.fileStatus.numberOfPages ? this.fileStatus.numberOfPages : 0;
}
private get _status(): FileStatus.StatusEnum {
return this.fileStatus.status === FileStatus.StatusEnum.REPROCESS || this.fileStatus.status === FileStatus.StatusEnum.FULLREPROCESS
? FileStatus.StatusEnum.PROCESSING
: this.fileStatus.status;
}
}

View File

@ -0,0 +1,142 @@
import { IListable } from '@iqser/common-ui';
import { FileAttributes, FileAttributesConfig, FileStatus, FileStatuses, IFile, List } from '@redaction/red-ui-http';
import { StatusSorter } from '@utils/sorters/status-sorter';
const processingStatuses: List<FileStatus> = [
FileStatuses.REPROCESS,
FileStatuses.FULLREPROCESS,
FileStatuses.OCR_PROCESSING,
FileStatuses.INDEXING,
FileStatuses.PROCESSING
] as const;
export class File implements IFile, IListable {
readonly added: string;
readonly allManualRedactionsApplied: boolean;
readonly analysisDuration: number;
readonly analysisRequired: boolean;
readonly approvalDate: string;
readonly currentReviewer: string;
readonly dictionaryVersion: number;
readonly dossierDictionaryVersion: number;
readonly dossierId: string;
readonly excluded: boolean;
readonly fileAttributes: FileAttributes;
readonly fileId: string;
readonly filename: string;
readonly hasAnnotationComments: boolean;
readonly hasHints: boolean;
readonly hasImages: boolean;
readonly hasRedactions: boolean;
readonly hasUpdates: boolean;
readonly lastOCRTime: string;
readonly lastProcessed: string;
readonly lastReviewer: string;
readonly lastUpdated: string;
readonly lastUploaded: string;
readonly legalBasisVersion: number;
readonly numberOfAnalyses: number;
readonly numberOfPages: number;
readonly rulesVersion: number;
readonly status: FileStatus;
readonly uploader: string;
readonly excludedPages: number[];
readonly hasSuggestions: boolean;
readonly dossierTemplateId: string;
primaryAttribute: string;
lastOpened: boolean;
readonly statusSort: number;
readonly cacheIdentifier: string;
readonly hintsOnly: boolean;
readonly hasNone: boolean;
readonly isUnassigned: boolean;
readonly isError: boolean;
readonly isProcessing: boolean;
readonly isApproved: boolean;
readonly isPending: boolean;
readonly isUnderReview: boolean;
readonly isUnderApproval: boolean;
readonly canBeApproved: boolean;
readonly canBeOpened: boolean;
readonly isWorkable: boolean;
readonly canBeOCRed: boolean;
constructor(file: IFile, public reviewerName: string, fileAttributesConfig?: FileAttributesConfig) {
this.added = file.added;
this.allManualRedactionsApplied = file.allManualRedactionsApplied;
this.analysisDuration = file.analysisDuration;
this.analysisRequired = file.analysisRequired && !file.excluded;
this.approvalDate = file.approvalDate;
this.currentReviewer = file.currentReviewer;
this.dictionaryVersion = file.dictionaryVersion;
this.dossierDictionaryVersion = file.dossierDictionaryVersion;
this.dossierId = file.dossierId;
this.excluded = file.excluded;
this.fileAttributes = file.fileAttributes;
this.fileId = file.fileId;
this.filename = file.filename;
this.hasAnnotationComments = file.hasAnnotationComments;
this.hasHints = file.hasHints;
this.hasImages = file.hasImages;
this.hasRedactions = file.hasRedactions;
this.hasUpdates = file.hasUpdates;
this.lastOCRTime = file.lastOCRTime;
this.lastProcessed = file.lastProcessed;
this.lastReviewer = file.lastReviewer;
this.lastUpdated = file.lastUpdated;
this.lastUploaded = file.lastUploaded;
this.legalBasisVersion = file.legalBasisVersion;
this.numberOfAnalyses = file.numberOfAnalyses;
this.status = ['REPROCESS', 'FULLREPROCESS'].includes(file.status) ? FileStatuses.PROCESSING : file.status;
this.isError = this.status === FileStatuses.ERROR;
this.numberOfPages = this.isError ? -1 : file.numberOfPages ?? 0;
this.rulesVersion = file.rulesVersion;
this.uploader = file.uploader;
this.excludedPages = file.excludedPages;
this.hasSuggestions = file.hasSuggestions;
this.dossierTemplateId = file.dossierTemplateId;
this.statusSort = StatusSorter[this.status];
this.cacheIdentifier = btoa(this.lastUploaded + this.lastOCRTime);
this.hintsOnly = this.hasHints && !this.hasRedactions;
this.hasNone = !this.hasRedactions && !this.hasHints && !this.hasSuggestions;
this.isUnassigned = !this.currentReviewer;
this.isProcessing = processingStatuses.includes(this.status);
this.isApproved = this.status === FileStatuses.APPROVED;
this.isPending = this.status === FileStatuses.UNPROCESSED;
this.isUnderReview = this.status === FileStatuses.UNDER_REVIEW;
this.isUnderApproval = this.status === FileStatuses.UNDER_APPROVAL;
this.canBeApproved = !this.analysisRequired && !this.hasSuggestions;
this.canBeOpened = !this.isError && !this.isPending;
this.isWorkable = !this.isProcessing && this.canBeOpened;
this.canBeOCRed = !this.excluded && !this.lastOCRTime && ['UNASSIGNED', 'UNDER_REVIEW', 'UNDER_APPROVAL'].includes(this.status);
if (fileAttributesConfig) {
const primary = fileAttributesConfig.fileAttributeConfigs?.find(c => c.primaryAttribute);
if (primary && file.fileAttributes?.attributeIdToValue) {
this.primaryAttribute = file.fileAttributes?.attributeIdToValue[primary.id];
}
if (!this.primaryAttribute) {
// Fallback here
this.primaryAttribute = '-';
}
}
if (!this.fileAttributes || !this.fileAttributes.attributeIdToValue) {
this.fileAttributes = { attributeIdToValue: {} };
}
}
get id(): string {
return this.fileId;
}
get searchKey(): string {
return this.filename;
}
get routerLink(): string | undefined {
return this.canBeOpened ? `/main/dossiers/${this.dossierId}/file/${this.fileId}` : undefined;
}
}

View File

@ -0,0 +1,41 @@
import { IListable } from '@iqser/common-ui';
import { ITypeValue } from '@redaction/red-ui-http';
export class TypeValue implements ITypeValue, IListable {
readonly type: string;
readonly addToDictionaryAction: boolean;
readonly caseInsensitive: boolean;
readonly description?: string;
readonly dossierTemplateId?: string;
readonly hexColor?: string;
readonly label?: string;
readonly hint: boolean;
readonly rank?: number;
readonly recommendation: boolean;
entries: string[] = [];
constructor(typeValue: ITypeValue, readonly virtual = false) {
this.type = typeValue.type;
this.addToDictionaryAction = !!typeValue.addToDictionaryAction;
this.caseInsensitive = !!typeValue.caseInsensitive;
this.description = typeValue.description;
this.dossierTemplateId = typeValue.dossierTemplateId;
this.hexColor = typeValue.hexColor;
this.hint = !!typeValue.hint;
this.rank = typeValue.rank;
this.recommendation = !!typeValue.recommendation;
this.label = typeValue.label;
}
get id(): string {
return this.type;
}
get searchKey(): string {
return this.type;
}
get routerLink(): string {
return `/main/admin/dossier-templates/${this.dossierTemplateId}/dictionaries/${this.type}`;
}
}

View File

@ -1,50 +0,0 @@
import { Listable } from '@iqser/common-ui';
import { TypeValue } from '@redaction/red-ui-http';
export class TypeValueWrapper implements Listable {
entries: string[] = [];
constructor(public typeValue: TypeValue, public label?: string, public virtual?: boolean) {
this.label = label || typeValue.label;
}
get id() {
return this.typeValue.type;
}
get addToDictionaryAction() {
return this.typeValue.addToDictionaryAction;
}
get caseInsensitive() {
return this.typeValue.caseInsensitive;
}
get description() {
return this.typeValue.description;
}
get dossierTemplateId() {
return this.typeValue.dossierTemplateId;
}
get hexColor() {
return this.typeValue.hexColor;
}
get hint() {
return this.typeValue.hint;
}
get rank() {
return this.typeValue.rank;
}
get recommendation() {
return this.typeValue.recommendation;
}
get type() {
return this.typeValue.type;
}
}

View File

@ -0,0 +1,28 @@
import { IUser, List } from '@redaction/red-ui-http';
import { IListable } from '@iqser/common-ui';
import { KeycloakProfile } from 'keycloak-js';
export class User implements IUser, IListable {
readonly email: string;
readonly username: string;
readonly firstName: string;
readonly lastName: string;
readonly name: string;
readonly searchKey: string;
readonly isActive = this.roles.length > 0;
readonly isManager = this.roles.indexOf('RED_MANAGER') >= 0;
readonly isUserAdmin = this.roles.indexOf('RED_USER_ADMIN') >= 0;
readonly isUser = this.roles.indexOf('RED_USER') >= 0;
readonly isAdmin = this.roles.indexOf('RED_ADMIN') >= 0;
readonly hasAnyREDRoles = this.isUser || this.isManager || this.isAdmin || this.isUserAdmin;
constructor(user: KeycloakProfile | IUser, readonly roles: List, readonly id: string) {
this.email = user.email;
this.username = user.username || this.email;
this.firstName = user.firstName;
this.lastName = user.lastName;
this.name = this.firstName && this.lastName ? `${this.firstName} ${this.lastName}` : this.username;
this.searchKey = this.name + this.username + this.email;
}
}

View File

@ -1,14 +1,15 @@
import { Component, Inject } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { DictionaryControllerService, TypeValue } from '@redaction/red-ui-http';
import { ITypeValue } from '@redaction/red-ui-http';
import { Observable } from 'rxjs';
import { Toaster } from '@iqser/common-ui';
import { TranslateService } from '@ngx-translate/core';
import { TypeValueWrapper } from '@models/file/type-value.wrapper';
import { TypeValue } from '@models/file/type-value';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { AppStateService } from '@state/app-state.service';
import { toKebabCase } from '@utils/functions';
import { DictionaryService } from '@shared/services/dictionary.service';
@Component({
selector: 'redaction-add-edit-dictionary-dialog',
@ -17,19 +18,19 @@ import { toKebabCase } from '@utils/functions';
})
export class AddEditDictionaryDialogComponent {
dictionaryForm: FormGroup;
readonly dictionary: TypeValueWrapper;
readonly dictionary: TypeValue;
technicalName = '';
private readonly _dossierTemplateId: string;
constructor(
private readonly _dictionaryControllerService: DictionaryControllerService,
private readonly _dictionaryService: DictionaryService,
private readonly _appStateService: AppStateService,
private readonly _formBuilder: FormBuilder,
private readonly _toaster: Toaster,
private readonly _translateService: TranslateService,
private readonly _dialogRef: MatDialogRef<AddEditDictionaryDialogComponent>,
@Inject(MAT_DIALOG_DATA)
private readonly _data: { dictionary: TypeValueWrapper; dossierTemplateId: string }
private readonly _data: { dictionary: TypeValue; dossierTemplateId: string }
) {
this.dictionary = _data.dictionary;
this._dossierTemplateId = _data.dossierTemplateId;
@ -81,17 +82,16 @@ export class AddEditDictionaryDialogComponent {
return false;
}
async saveDictionary() {
const typeValue: TypeValue = this._formToObject();
let observable: Observable<any>;
async saveDictionary(): Promise<void> {
const typeValue: ITypeValue = this._formToObject();
let observable: Observable<unknown>;
if (this.dictionary) {
// edit mode
observable = this._dictionaryControllerService.updateType(typeValue, this._dossierTemplateId, typeValue.type);
observable = this._dictionaryService.updateType(typeValue, this._dossierTemplateId, typeValue.type);
} else {
// create mode
typeValue.dossierTemplateId = this._dossierTemplateId;
observable = this._dictionaryControllerService.addType(typeValue);
observable = this._dictionaryService.addType({ ...typeValue, dossierTemplateId: this._dossierTemplateId });
}
observable.subscribe(
@ -120,7 +120,7 @@ export class AddEditDictionaryDialogComponent {
this.technicalName = technicalName;
}
private _formToObject(): TypeValue {
private _formToObject(): ITypeValue {
return {
type: this.dictionary?.type || this.technicalName,
label: this.dictionaryForm.get('label').value,

View File

@ -1,10 +1,9 @@
import { Component, Inject, OnDestroy } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { DossierAttributeConfig, FileAttributeConfig } from '@redaction/red-ui-http';
import { DossierAttributeConfigTypes, FileAttributeConfigTypes, IDossierAttributeConfig } from '@redaction/red-ui-http';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { AutoUnsubscribe, LoadingService } from '@iqser/common-ui';
import { AutoUnsubscribe, LoadingService, Toaster } from '@iqser/common-ui';
import { HttpErrorResponse } from '@angular/common/http';
import { Toaster } from '@iqser/common-ui';
import { DossierAttributesService } from '@shared/services/controller-wrappers/dossier-attributes.service';
import { dossierAttributeTypesTranslations } from '../../translations/dossier-attribute-types-translations';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
@ -15,14 +14,9 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
})
export class AddEditDossierAttributeDialogComponent extends AutoUnsubscribe implements OnDestroy {
dossierAttributeForm: FormGroup;
dossierAttribute: DossierAttributeConfig;
dossierAttribute: IDossierAttributeConfig;
readonly translations = dossierAttributeTypesTranslations;
readonly typeOptions = [
DossierAttributeConfig.TypeEnum.TEXT,
DossierAttributeConfig.TypeEnum.NUMBER,
DossierAttributeConfig.TypeEnum.DATE,
DossierAttributeConfig.TypeEnum.IMAGE
];
readonly typeOptions = Object.keys(DossierAttributeConfigTypes);
constructor(
private readonly _formBuilder: FormBuilder,
@ -31,7 +25,7 @@ export class AddEditDossierAttributeDialogComponent extends AutoUnsubscribe impl
private readonly _toaster: Toaster,
readonly dialogRef: MatDialogRef<AddEditDossierAttributeDialogComponent>,
@Inject(MAT_DIALOG_DATA)
readonly data: { readonly dossierAttribute: DossierAttributeConfig }
readonly data: { readonly dossierAttribute: IDossierAttributeConfig }
) {
super();
this.dossierAttribute = data.dossierAttribute;
@ -44,7 +38,7 @@ export class AddEditDossierAttributeDialogComponent extends AutoUnsubscribe impl
disabled: true
}
}),
type: [this.dossierAttribute?.type || FileAttributeConfig.TypeEnum.TEXT, Validators.required]
type: [this.dossierAttribute?.type || FileAttributeConfigTypes.TEXT, Validators.required]
});
}
@ -65,7 +59,7 @@ export class AddEditDossierAttributeDialogComponent extends AutoUnsubscribe impl
saveFileAttribute() {
this._loadingService.start();
const attribute: DossierAttributeConfig = {
const attribute: IDossierAttributeConfig = {
id: this.dossierAttribute?.id,
editable: true,
...this.dossierAttributeForm.getRawValue()

View File

@ -5,9 +5,9 @@ import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import * as moment from 'moment';
import { Moment } from 'moment';
import {
Dossier,
DossierTemplateControllerService,
DossierTemplateModel,
DownloadFileType,
IDossierTemplate,
ReportTemplate,
ReportTemplateControllerService
} from '@redaction/red-ui-http';
@ -23,8 +23,8 @@ export class AddEditDossierTemplateDialogComponent implements OnInit {
dossierTemplateForm: FormGroup;
hasValidFrom: boolean;
hasValidTo: boolean;
downloadTypesEnum: Dossier.DownloadFileTypesEnum[] = ['ORIGINAL', 'PREVIEW', 'REDACTED'];
downloadTypes: { key: Dossier.DownloadFileTypesEnum; label: string }[] = this.downloadTypesEnum.map(type => ({
downloadTypesEnum: DownloadFileType[] = ['ORIGINAL', 'PREVIEW', 'REDACTED'];
downloadTypes: { key: DownloadFileType; label: string }[] = this.downloadTypesEnum.map(type => ({
key: type,
label: downloadTypesTranslations[type]
}));
@ -39,7 +39,7 @@ export class AddEditDossierTemplateDialogComponent implements OnInit {
private readonly _dossierTemplateController: DossierTemplateControllerService,
private readonly _reportTemplateController: ReportTemplateControllerService,
public dialogRef: MatDialogRef<AddEditDossierTemplateDialogComponent>,
@Inject(MAT_DIALOG_DATA) public dossierTemplate: DossierTemplateModel
@Inject(MAT_DIALOG_DATA) readonly dossierTemplate: IDossierTemplate
) {
this.dossierTemplateForm = this._formBuilder.group({
name: [this.dossierTemplate?.name, Validators.required],

View File

@ -1,7 +1,7 @@
import { Component, Inject } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { AppStateService } from '@state/app-state.service';
import { FileAttributeConfig } from '@redaction/red-ui-http';
import { FileAttributeConfigTypes, IFileAttributeConfig } from '@redaction/red-ui-http';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { fileAttributeTypesTranslations } from '../../translations/file-attribute-types-translations';
@ -12,9 +12,9 @@ import { fileAttributeTypesTranslations } from '../../translations/file-attribut
})
export class AddEditFileAttributeDialogComponent {
fileAttributeForm: FormGroup;
fileAttribute: FileAttributeConfig;
fileAttribute: IFileAttributeConfig;
dossierTemplateId: string;
readonly typeOptions = [FileAttributeConfig.TypeEnum.TEXT, FileAttributeConfig.TypeEnum.NUMBER, FileAttributeConfig.TypeEnum.DATE];
readonly typeOptions = Object.keys(FileAttributeConfigTypes);
translations = fileAttributeTypesTranslations;
constructor(
@ -22,7 +22,7 @@ export class AddEditFileAttributeDialogComponent {
private readonly _formBuilder: FormBuilder,
public dialogRef: MatDialogRef<AddEditFileAttributeDialogComponent>,
@Inject(MAT_DIALOG_DATA)
public data: { fileAttribute: FileAttributeConfig; dossierTemplateId: string }
public data: { fileAttribute: IFileAttributeConfig; dossierTemplateId: string }
) {
this.fileAttribute = data.fileAttribute;
this.dossierTemplateId = data.dossierTemplateId;
@ -30,7 +30,7 @@ export class AddEditFileAttributeDialogComponent {
this.fileAttributeForm = this._formBuilder.group({
label: [this.fileAttribute?.label, Validators.required],
csvColumnHeader: [this.fileAttribute?.csvColumnHeader, Validators.required],
type: [this.fileAttribute?.type || FileAttributeConfig.TypeEnum.TEXT, Validators.required],
type: [this.fileAttribute?.type || FileAttributeConfigTypes.TEXT, Validators.required],
readonly: [this.fileAttribute ? !this.fileAttribute.editable : false],
primaryAttribute: [this.fileAttribute?.primaryAttribute],
filterable: [this.fileAttribute?.filterable],
@ -57,7 +57,7 @@ export class AddEditFileAttributeDialogComponent {
}
async saveFileAttribute() {
const fileAttribute: FileAttributeConfig = {
const fileAttribute: IFileAttributeConfig = {
id: this.fileAttribute?.id,
editable: !this.fileAttributeForm.get('readonly').value,
...this.fileAttributeForm.getRawValue()

View File

@ -1,6 +1,6 @@
import { Component, Inject } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { UserWrapper } from '@services/user.service';
import { User } from '@models/user';
@Component({
selector: 'redaction-add-edit-user-dialog',
@ -10,7 +10,7 @@ import { UserWrapper } from '@services/user.service';
export class AddEditUserDialogComponent {
resettingPassword = false;
constructor(readonly dialogRef: MatDialogRef<AddEditUserDialogComponent>, @Inject(MAT_DIALOG_DATA) readonly user: UserWrapper) {}
constructor(readonly dialogRef: MatDialogRef<AddEditUserDialogComponent>, @Inject(MAT_DIALOG_DATA) readonly user: User) {}
toggleResetPassword() {
this.resettingPassword = !this.resettingPassword;

View File

@ -1,8 +1,9 @@
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { FormBuilder, Validators } from '@angular/forms';
import { UserControllerService } from '@redaction/red-ui-http';
import { UserService, UserWrapper } from '@services/user.service';
import { UserService } from '@services/user.service';
import { LoadingService } from '@iqser/common-ui';
import { User } from '@models/user';
@Component({
selector: 'redaction-reset-password',
@ -13,8 +14,8 @@ export class ResetPasswordComponent {
readonly passwordForm = this._formBuilder.group({
temporaryPassword: [null, Validators.required]
});
@Input() user: UserWrapper;
@Output() toggleResetPassword = new EventEmitter();
@Input() user: User;
@Output() readonly toggleResetPassword = new EventEmitter();
constructor(
private readonly _formBuilder: FormBuilder,

View File

@ -4,8 +4,8 @@ import { UserControllerService } from '@redaction/red-ui-http';
import { AdminDialogService } from '../../../services/admin-dialog.service';
import { IconButtonTypes, LoadingService, Toaster } from '@iqser/common-ui';
import { rolesTranslations } from '../../../../../translations/roles-translations';
import { UserWrapper } from '@services/user.service';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { User } from '@models/user';
@Component({
selector: 'redaction-user-details',
@ -15,9 +15,10 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
export class UserDetailsComponent implements OnInit {
readonly iconButtonTypes = IconButtonTypes;
@Input() user: UserWrapper;
@Output() toggleResetPassword = new EventEmitter();
@Output() closeDialog = new EventEmitter<any>();
@Input() user: User;
@Output() readonly toggleResetPassword = new EventEmitter();
@Output() readonly closeDialog = new EventEmitter();
userForm: FormGroup;
readonly ROLES = ['RED_USER', 'RED_MANAGER', 'RED_USER_ADMIN', 'RED_ADMIN'];
readonly translations = rolesTranslations;

View File

@ -1,5 +1,5 @@
import { Component, Inject } from '@angular/core';
import { FileAttributeConfig } from '@redaction/red-ui-http';
import { IFileAttributeConfig } from '@redaction/red-ui-http';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
@ -9,7 +9,7 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
styleUrls: ['./confirm-delete-file-attribute-dialog.component.scss']
})
export class ConfirmDeleteFileAttributeDialogComponent {
fileAttribute: FileAttributeConfig;
fileAttribute: IFileAttributeConfig;
checkboxes = [
{ value: false, label: _('confirm-delete-file-attribute.impacted-documents') },
{ value: false, label: _('confirm-delete-file-attribute.lost-details') }
@ -18,7 +18,7 @@ export class ConfirmDeleteFileAttributeDialogComponent {
constructor(
public dialogRef: MatDialogRef<ConfirmDeleteFileAttributeDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: FileAttributeConfig
@Inject(MAT_DIALOG_DATA) public data: IFileAttributeConfig
) {
this.fileAttribute = data;
}

View File

@ -4,7 +4,7 @@ import { UserControllerService } from '@redaction/red-ui-http';
import { AppStateService } from '@state/app-state.service';
import { LoadingService } from '@iqser/common-ui';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { UserWrapper } from '@services/user.service';
import { User } from '@models/user';
@Component({
selector: 'redaction-confirm-delete-users-dialog',
@ -24,7 +24,7 @@ export class ConfirmDeleteUsersDialogComponent {
private readonly _loadingService: LoadingService,
private readonly _userControllerService: UserControllerService,
readonly dialogRef: MatDialogRef<ConfirmDeleteUsersDialogComponent>,
@Inject(MAT_DIALOG_DATA) readonly users: UserWrapper[]
@Inject(MAT_DIALOG_DATA) readonly users: User[]
) {
this.dossiersCount = this._appStateService.allDossiers.filter(dw => {
for (const user of this.users) {

View File

@ -1,12 +1,13 @@
import { Component, Inject } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { Colors, DictionaryControllerService } from '@redaction/red-ui-http';
import { Colors } from '@redaction/red-ui-http';
import { Toaster } from '@iqser/common-ui';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { TranslateService } from '@ngx-translate/core';
import { DefaultColorType } from '@models/default-color-key.model';
import { defaultColorsTranslations } from '../../translations/default-colors-translations';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { DictionaryService } from '@shared/services/dictionary.service';
@Component({
selector: 'redaction-edit-color-dialog',
@ -23,7 +24,7 @@ export class EditColorDialogComponent {
constructor(
private readonly _formBuilder: FormBuilder,
private readonly _dictionaryControllerService: DictionaryControllerService,
private readonly _dictionaryService: DictionaryService,
private readonly _toaster: Toaster,
private readonly _translateService: TranslateService,
private readonly _dialogRef: MatDialogRef<EditColorDialogComponent>,
@ -51,7 +52,7 @@ export class EditColorDialogComponent {
};
try {
await this._dictionaryControllerService.setColors(colors, this._dossierTemplateId).toPromise();
await this._dictionaryService.setColors(colors, this._dossierTemplateId).toPromise();
this._dialogRef.close(true);
const color = this._translateService.instant(defaultColorsTranslations[this.colorKey]);
this._toaster.info(_('edit-color-dialog.success'), { params: { color: color } });

View File

@ -12,7 +12,7 @@ import {
ViewChild
} from '@angular/core';
import { Field } from '../file-attributes-csv-import-dialog.component';
import { FileAttributeConfig } from '@redaction/red-ui-http';
import { FileAttributeConfigTypes } from '@redaction/red-ui-http';
import { CircleButtonTypes, DefaultListingServices, ListingComponent, TableColumnConfig } from '@iqser/common-ui';
import { fileAttributeTypesTranslations } from '../../../translations/file-attribute-types-translations';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
@ -28,20 +28,15 @@ export class ActiveFieldsListingComponent extends ListingComponent<Field> implem
readonly translations = fileAttributeTypesTranslations;
readonly tableHeaderLabel = _('file-attributes-csv-import.table-header.title');
tableColumnConfigs: TableColumnConfig<Field>[];
readonly typeOptions = [
FileAttributeConfig.TypeEnum.TEXT,
FileAttributeConfig.TypeEnum.NUMBER,
FileAttributeConfig.TypeEnum.DATE
] as const;
readonly typeOptions = Object.keys(FileAttributeConfigTypes);
@ViewChild('labelTemplate', { static: true }) labelTemplate: TemplateRef<never>;
@ViewChild('typeTemplate', { static: true }) typeTemplate: TemplateRef<never>;
@ViewChild('readonlyTemplate', { static: true }) readonlyTemplate: TemplateRef<never>;
@ViewChild('primaryTemplate', { static: true }) primaryTemplate: TemplateRef<never>;
@Input() entities: Field[];
@Output() entitiesChange = new EventEmitter<Field[]>();
@Output() setHoveredColumn = new EventEmitter<string>();
@Output() toggleFieldActive = new EventEmitter<Field>();
protected readonly _primaryKey = 'csvColumn';
@Output() readonly entitiesChange = new EventEmitter<Field[]>();
@Output() readonly setHoveredColumn = new EventEmitter<string>();
@Output() readonly toggleFieldActive = new EventEmitter<Field>();
constructor(protected readonly _injector: Injector) {
super(_injector);

View File

@ -100,7 +100,7 @@
(click)="toggleFieldActive(field)"
(mouseenter)="setHoveredColumn(field.csvColumn)"
(mouseleave)="setHoveredColumn()"
*ngFor="let field of sortedDisplayedEntities$ | async; trackBy: trackByPrimaryKey"
*ngFor="let field of sortedDisplayedEntities$ | async"
class="csv-header-pill-wrapper"
>
<div [class.selected]="isActive(field)" class="csv-header-pill">

View File

@ -2,17 +2,23 @@ import { Component, Inject, Injector } from '@angular/core';
import { AbstractControl, FormBuilder, FormGroup, ValidatorFn, Validators } from '@angular/forms';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import * as Papa from 'papaparse';
import { FileAttributeConfig, FileAttributesConfig, FileAttributesControllerService } from '@redaction/red-ui-http';
import {
FileAttributeConfigType,
FileAttributeConfigTypes,
FileAttributesConfig,
FileAttributesControllerService
} from '@redaction/red-ui-http';
import { Observable } from 'rxjs';
import { map, startWith } from 'rxjs/operators';
import { DefaultListingServices, Listable, ListingComponent, TableColumnConfig, Toaster } from '@iqser/common-ui';
import { DefaultListingServices, IListable, ListingComponent, TableColumnConfig, Toaster } from '@iqser/common-ui';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { FileAttributeConfig } from '@models/file/file-attribute-config';
export interface Field extends Listable {
export interface Field extends IListable {
id: string;
csvColumn: string;
name: string;
type: FileAttributeConfig.TypeEnum;
type: FileAttributeConfigType;
readonly: boolean;
primaryAttribute: boolean;
}
@ -35,7 +41,6 @@ export class FileAttributesCsvImportDialogComponent extends ListingComponent<Fie
columnSample = [];
initialParseConfig: { delimiter?: string; encoding?: string } = {};
readonly tableHeaderLabel = '';
protected readonly _primaryKey = 'csvColumn';
constructor(
private readonly _toaster: Toaster,
@ -148,7 +153,7 @@ export class FileAttributesCsvImportDialogComponent extends ListingComponent<Fie
toggleFieldActive(field: Field) {
if (!this.isActive(field)) {
this.activeFields = [...this.activeFields, field];
this.activeFields = [...this.activeFields, { ...field, searchKey: field.csvColumn }];
} else {
this.activeFields.splice(this.activeFields.indexOf(field), 1);
this.activeFields = [...this.activeFields];
@ -156,7 +161,7 @@ export class FileAttributesCsvImportDialogComponent extends ListingComponent<Fie
}
activateAll() {
this.activeFields = [...this.allEntities];
this.activeFields = [...this.allEntities.map(item => ({ ...item, searchKey: item.csvColumn }))];
}
deactivateAll() {
@ -165,17 +170,15 @@ export class FileAttributesCsvImportDialogComponent extends ListingComponent<Fie
async save() {
const newPrimary = !!this.activeFields.find(attr => attr.primaryAttribute);
let fileAttributeConfigs = this.data.existingConfiguration.fileAttributeConfigs;
if (newPrimary) {
this.data.existingConfiguration.fileAttributeConfigs.forEach(attr => (attr.primaryAttribute = false));
fileAttributeConfigs = fileAttributeConfigs.map(attr => new FileAttributeConfig({ ...attr, primaryAttribute: false }));
}
const fileAttributes = {
...this.baseConfigForm.getRawValue(),
fileAttributeConfigs: [
...this.data.existingConfiguration.fileAttributeConfigs.filter(
a => !this.allEntities.find(entity => entity.csvColumn === a.csvColumnHeader)
),
...fileAttributeConfigs.filter(a => !this.allEntities.find(entity => entity.csvColumn === a.csvColumnHeader)),
...this.activeFields.map(field => ({
id: field.id,
csvColumnHeader: field.csvColumn,
@ -228,7 +231,8 @@ export class FileAttributesCsvImportDialogComponent extends ListingComponent<Fie
id: btoa(csvColumn),
csvColumn,
name: csvColumn,
type: isNumber ? FileAttributeConfig.TypeEnum.NUMBER : FileAttributeConfig.TypeEnum.TEXT,
searchKey: csvColumn,
type: isNumber ? FileAttributeConfigTypes.NUMBER : FileAttributeConfigTypes.TEXT,
readonly: false,
primaryAttribute: false
};

View File

@ -1,13 +1,13 @@
import { Component, forwardRef, Injector, OnDestroy, OnInit, TemplateRef, ViewChild } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';
import { AuditControllerService, AuditModel, AuditResponse, AuditSearchRequest } from '@redaction/red-ui-http';
import { AuditControllerService, AuditResponse, AuditSearchRequest, IAudit } from '@redaction/red-ui-http';
import { Moment } from 'moment';
import { applyIntervalConstraints } from '@utils/date-inputs-utils';
import { DefaultListingServices, KeysOf, ListingComponent, LoadingService, TableColumnConfig } from '@iqser/common-ui';
import { DefaultListingServices, ListingComponent, LoadingService, TableColumnConfig } from '@iqser/common-ui';
import { auditCategoriesTranslations } from '../../translations/audit-categories-translations';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { UserService } from '@services/user.service';
import { AuditModelWrapper } from '../../../../models/audit-model-wrapper.model';
import { Audit } from '@models/audit.model';
const PAGE_SIZE = 50;
@ -17,22 +17,21 @@ const PAGE_SIZE = 50;
styleUrls: ['./audit-screen.component.scss'],
providers: [...DefaultListingServices, { provide: ListingComponent, useExisting: forwardRef(() => AuditScreenComponent) }]
})
export class AuditScreenComponent extends ListingComponent<AuditModelWrapper> implements OnDestroy, OnInit {
export class AuditScreenComponent extends ListingComponent<Audit> implements OnDestroy, OnInit {
readonly ALL_CATEGORIES = 'allCategories';
readonly ALL_USERS = _('audit-screen.all-users');
readonly translations = auditCategoriesTranslations;
readonly currentUser = this._userService.currentUser;
@ViewChild('messageTemplate', { static: true }) messageTemplate: TemplateRef<never>;
@ViewChild('dateTemplate', { static: true }) dateTemplate: TemplateRef<never>;
@ViewChild('userTemplate', { static: true }) userTemplate: TemplateRef<never>;
@ViewChild('categoryTemplate', { static: true }) categoryTemplate: TemplateRef<never>;
@ViewChild('messageTemplate', { static: true }) messageTemplate: TemplateRef<unknown>;
@ViewChild('dateTemplate', { static: true }) dateTemplate: TemplateRef<unknown>;
@ViewChild('userTemplate', { static: true }) userTemplate: TemplateRef<unknown>;
@ViewChild('categoryTemplate', { static: true }) categoryTemplate: TemplateRef<unknown>;
filterForm: FormGroup;
categories: string[] = [];
userIds: Set<string>;
logs: AuditResponse;
tableColumnConfigs: TableColumnConfig<AuditModelWrapper>[];
tableColumnConfigs: TableColumnConfig<Audit>[];
readonly tableHeaderLabel = _('audit-screen.table-header.title');
protected readonly _primaryKey: KeysOf<AuditModelWrapper> = 'recordDate';
private _previousFrom: Moment;
private _previousTo: Moment;
@ -133,7 +132,7 @@ export class AuditScreenComponent extends ListingComponent<AuditModelWrapper> im
this.categories = data[0].map(c => c.category);
this.categories.splice(0, 0, this.ALL_CATEGORIES);
this.logs = data[1];
const entities = this.logs.data.map((log: AuditModel) => new AuditModelWrapper(log));
const entities = this.logs.data.map((log: IAudit) => new Audit(log));
this.entitiesService.setEntities(entities);
this.userIds = new Set<string>([this.ALL_USERS]);
for (const id of this.logs.data.map(log => log.userId).filter(uid => !!uid)) {

View File

@ -1,15 +1,23 @@
import { ChangeDetectionStrategy, Component, forwardRef, Injector, OnInit, TemplateRef, ViewChild } from '@angular/core';
import { AppStateService } from '@state/app-state.service';
import { Colors, DictionaryControllerService } from '@redaction/red-ui-http';
import { Colors } from '@redaction/red-ui-http';
import { ActivatedRoute } from '@angular/router';
import { AdminDialogService } from '../../services/admin-dialog.service';
import { CircleButtonTypes, DefaultListingServices, Listable, ListingComponent, LoadingService, TableColumnConfig } from '@iqser/common-ui';
import {
CircleButtonTypes,
DefaultListingServices,
IListable,
ListingComponent,
LoadingService,
TableColumnConfig
} from '@iqser/common-ui';
import { DefaultColorType } from '@models/default-color-key.model';
import { defaultColorsTranslations } from '../../translations/default-colors-translations';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { UserService } from '@services/user.service';
import { DictionaryService } from '@shared/services/dictionary.service';
interface ListItem extends Listable {
interface ListItem extends IListable {
readonly key: string;
readonly value: string;
}
@ -21,14 +29,13 @@ interface ListItem extends Listable {
providers: [...DefaultListingServices, { provide: ListingComponent, useExisting: forwardRef(() => DefaultColorsScreenComponent) }]
})
export class DefaultColorsScreenComponent extends ListingComponent<ListItem> implements OnInit {
@ViewChild('nameTemplate', { static: true }) nameTemplate: TemplateRef<never>;
@ViewChild('colorTemplate', { static: true }) colorTemplate: TemplateRef<never>;
@ViewChild('nameTemplate', { static: true }) nameTemplate: TemplateRef<unknown>;
@ViewChild('colorTemplate', { static: true }) colorTemplate: TemplateRef<unknown>;
readonly circleButtonTypes = CircleButtonTypes;
readonly currentUser = this._userService.currentUser;
readonly translations = defaultColorsTranslations;
readonly tableHeaderLabel = _('default-colors-screen.table-header.title');
tableColumnConfigs: TableColumnConfig<ListItem>[];
protected readonly _primaryKey = 'key';
private _colorsObj: Colors;
constructor(
@ -38,7 +45,7 @@ export class DefaultColorsScreenComponent extends ListingComponent<ListItem> imp
private readonly _activatedRoute: ActivatedRoute,
private readonly _appStateService: AppStateService,
private readonly _dialogService: AdminDialogService,
private readonly _dictionaryControllerService: DictionaryControllerService
private readonly _dictionaryService: DictionaryService
) {
super(_injector);
_appStateService.activateDossierTemplate(_activatedRoute.snapshot.params.dossierTemplateId);
@ -69,7 +76,7 @@ export class DefaultColorsScreenComponent extends ListingComponent<ListItem> imp
this.tableColumnConfigs = [
{
label: _('default-colors-screen.table-col-names.key'),
sortByKey: 'key',
sortByKey: 'searchKey',
template: this.nameTemplate,
width: '2fr'
},
@ -83,11 +90,12 @@ export class DefaultColorsScreenComponent extends ListingComponent<ListItem> imp
private async _loadColors() {
this._loadingService.start();
const data = await this._dictionaryControllerService.getColors(this._appStateService.activeDossierTemplateId).toPromise();
const data = await this._dictionaryService.getColors(this._appStateService.activeDossierTemplateId).toPromise();
this._colorsObj = data;
const entities = Object.keys(data).map(key => ({
const entities: ListItem[] = Object.keys(data).map(key => ({
id: key,
key,
searchKey: key,
value: data[key]
}));
this.entitiesService.setEntities(entities);

View File

@ -1,11 +1,10 @@
import { Component, forwardRef, Injector, OnInit, TemplateRef, ViewChild } from '@angular/core';
import { DoughnutChartConfig } from '@shared/components/simple-doughnut-chart/simple-doughnut-chart.component';
import { DictionaryControllerService } from '@redaction/red-ui-http';
import { AppStateService } from '@state/app-state.service';
import { catchError, defaultIfEmpty, tap } from 'rxjs/operators';
import { forkJoin, of } from 'rxjs';
import { ActivatedRoute } from '@angular/router';
import { TypeValueWrapper } from '@models/file/type-value.wrapper';
import { TypeValue } from '@models/file/type-value';
import { TranslateService } from '@ngx-translate/core';
import {
CircleButtonTypes,
@ -18,8 +17,9 @@ import {
import { AdminDialogService } from '../../services/admin-dialog.service';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { UserService } from '@services/user.service';
import { DictionaryService } from '@shared/services/dictionary.service';
const toChartConfig = (dict: TypeValueWrapper): DoughnutChartConfig => ({
const toChartConfig = (dict: TypeValue): DoughnutChartConfig => ({
value: dict.entries?.length ?? 0,
color: dict.hexColor,
label: dict.label,
@ -31,17 +31,16 @@ const toChartConfig = (dict: TypeValueWrapper): DoughnutChartConfig => ({
styleUrls: ['./dictionary-listing-screen.component.scss'],
providers: [...DefaultListingServices, { provide: ListingComponent, useExisting: forwardRef(() => DictionaryListingScreenComponent) }]
})
export class DictionaryListingScreenComponent extends ListingComponent<TypeValueWrapper> implements OnInit {
export class DictionaryListingScreenComponent extends ListingComponent<TypeValue> implements OnInit {
readonly iconButtonTypes = IconButtonTypes;
readonly circleButtonTypes = CircleButtonTypes;
readonly currentUser = this._userService.currentUser;
readonly tableHeaderLabel = _('dictionary-listing.table-header.title');
tableColumnConfigs: TableColumnConfig<TypeValueWrapper>[];
tableColumnConfigs: TableColumnConfig<TypeValue>[];
chartData: DoughnutChartConfig[] = [];
@ViewChild('labelTemplate', { static: true }) labelTemplate: TemplateRef<never>;
@ViewChild('rankTemplate', { static: true }) rankTemplate: TemplateRef<never>;
@ViewChild('iconTemplate', { static: true }) iconTemplate: TemplateRef<never>;
protected readonly _primaryKey = 'type';
constructor(
protected readonly _injector: Injector,
@ -51,15 +50,13 @@ export class DictionaryListingScreenComponent extends ListingComponent<TypeValue
private readonly _appStateService: AppStateService,
private readonly _dialogService: AdminDialogService,
private readonly _translateService: TranslateService,
private readonly _dictionaryControllerService: DictionaryControllerService
private readonly _dictionaryService: DictionaryService
) {
super(_injector);
_loadingService.start();
_appStateService.activateDossierTemplate(_activatedRoute.snapshot.params.dossierTemplateId);
}
routerLinkFn = (entity: TypeValueWrapper) => [entity.type];
ngOnInit(): void {
this._configureTableColumns();
this._loadDictionaryData();
@ -68,7 +65,7 @@ export class DictionaryListingScreenComponent extends ListingComponent<TypeValue
openDeleteDictionariesDialog($event?: MouseEvent, types = this.entitiesService.selected) {
this._dialogService.openDialog('confirm', $event, null, async () => {
this._loadingService.start();
await this._dictionaryControllerService
await this._dictionaryService
.deleteTypes(
types.map(t => t.type),
this._appStateService.activeDossierTemplateId
@ -82,7 +79,7 @@ export class DictionaryListingScreenComponent extends ListingComponent<TypeValue
});
}
openAddEditDictionaryDialog($event?: MouseEvent, dictionary?: TypeValueWrapper) {
openAddEditDictionaryDialog($event?: MouseEvent, dictionary?: TypeValue) {
this._dialogService.openDialog(
'addEditDictionary',
$event,
@ -104,7 +101,7 @@ export class DictionaryListingScreenComponent extends ListingComponent<TypeValue
this.tableColumnConfigs = [
{
label: _('dictionary-listing.table-col-names.type'),
sortByKey: 'label',
sortByKey: 'searchKey',
width: '2fr',
template: this.labelTemplate
},
@ -142,8 +139,8 @@ export class DictionaryListingScreenComponent extends ListingComponent<TypeValue
}
const dataObs = this.allEntities.map(dict =>
this._dictionaryControllerService.getDictionaryForType(this._appStateService.activeDossierTemplateId, dict.type).pipe(
tap(values => (dict.entries = values.entries ?? [])),
this._dictionaryService.getFor(this._appStateService.activeDossierTemplateId, dict.type).pipe(
tap(values => (dict.entries = [...values.entries] ?? [])),
catchError(() => {
dict.entries = [];
return of({});

View File

@ -1,5 +1,4 @@
import { Component, ElementRef, OnInit, ViewChild } from '@angular/core';
import { DictionaryControllerService } from '@redaction/red-ui-http';
import { AppStateService } from '@state/app-state.service';
import { ActivatedRoute, Router } from '@angular/router';
import { TranslateService } from '@ngx-translate/core';
@ -7,8 +6,8 @@ import { saveAs } from 'file-saver';
import { ComponentHasChanges } from '@guards/can-deactivate.guard';
import { AdminDialogService } from '../../services/admin-dialog.service';
import { DictionaryManagerComponent } from '@shared/components/dictionary-manager/dictionary-manager.component';
import { DictionarySaveService } from '@shared/services/dictionary-save.service';
import { TypeValueWrapper } from '@models/file/type-value.wrapper';
import { DictionaryService } from '@shared/services/dictionary.service';
import { TypeValue } from '@models/file/type-value';
import { CircleButtonTypes, LoadingService } from '@iqser/common-ui';
import { UserService } from '@services/user.service';
@ -21,7 +20,7 @@ export class DictionaryOverviewScreenComponent extends ComponentHasChanges imple
readonly currentUser = this._userService.currentUser;
entries: string[] = [];
dictionary: TypeValueWrapper;
dictionary: TypeValue;
@ViewChild('dictionaryManager', { static: false })
private readonly _dictionaryManager: DictionaryManagerComponent;
@ -35,8 +34,7 @@ export class DictionaryOverviewScreenComponent extends ComponentHasChanges imple
private readonly _appStateService: AppStateService,
private readonly _dialogService: AdminDialogService,
protected readonly _translateService: TranslateService,
private readonly _dictionarySaveService: DictionarySaveService,
private readonly _dictionaryControllerService: DictionaryControllerService
private readonly _dictionaryService: DictionaryService
) {
super(_translateService);
}
@ -51,7 +49,7 @@ export class DictionaryOverviewScreenComponent extends ComponentHasChanges imple
this._activatedRoute.snapshot.params.dossierTemplateId
);
this.dictionary = this._appStateService.activeDictionary;
this._loadEntries();
await this._loadEntries();
}
openEditDictionaryDialog($event: any) {
@ -75,7 +73,7 @@ export class DictionaryOverviewScreenComponent extends ComponentHasChanges imple
$event?.stopPropagation();
this._dialogService.openDialog('confirm', $event, null, async () => {
await this._dictionaryControllerService.deleteTypes([this.dictionary.type], this.dictionary.dossierTemplateId).toPromise();
await this._dictionaryService.deleteTypes([this.dictionary.type], this.dictionary.dossierTemplateId).toPromise();
await this._appStateService.loadDictionaryData();
await this._router.navigate([
'/main',
@ -110,30 +108,34 @@ export class DictionaryOverviewScreenComponent extends ComponentHasChanges imple
saveEntries(entries: string[]) {
this._loadingService.start();
this._dictionarySaveService
.saveEntries(entries, this.entries, this.dictionary.dossierTemplateId, this.dictionary.type, null)
.subscribe(
() => {
this._loadingService.stop();
this._loadEntries();
},
() => {
this._loadingService.stop();
}
);
}
private _loadEntries() {
this._loadingService.start();
this._dictionaryControllerService.getDictionaryForType(this.dictionary.dossierTemplateId, this.dictionary.type).subscribe(
data => {
this._loadingService.stop();
this.entries = data.entries.sort((str1, str2) => str1.localeCompare(str2, undefined, { sensitivity: 'accent' }));
this._dictionaryService.saveEntries(entries, this.entries, this.dictionary.dossierTemplateId, this.dictionary.type, null).subscribe(
async () => {
await this._loadEntries();
},
() => {
this._loadingService.stop();
this.entries = [];
}
);
}
private async _loadEntries() {
this._loadingService.start();
await this._dictionaryService
.getFor(this.dictionary.dossierTemplateId, this.dictionary.type)
.toPromise()
.then(
data => {
this._loadingService.stop();
this.entries = [...data.entries].sort((str1, str2) => str1.localeCompare(str2, undefined, { sensitivity: 'accent' }));
},
() => {
this._loadingService.stop();
this.entries = [];
}
)
.catch(() => {
this._loadingService.stop();
this.entries = [];
});
}
}

View File

@ -7,7 +7,7 @@ import {
LoadingService,
TableColumnConfig
} from '@iqser/common-ui';
import { DossierAttributeConfig } from '@redaction/red-ui-http';
import { IDossierAttributeConfig } from '@redaction/red-ui-http';
import { AppStateService } from '@state/app-state.service';
import { ActivatedRoute } from '@angular/router';
import { AdminDialogService } from '../../services/admin-dialog.service';
@ -15,6 +15,7 @@ import { DossierAttributesService } from '@shared/services/controller-wrappers/d
import { dossierAttributeTypesTranslations } from '../../translations/dossier-attribute-types-translations';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { UserService } from '@services/user.service';
import { DossierAttributeConfig } from '@state/model/dossier-attribute-config';
@Component({
templateUrl: './dossier-attributes-listing-screen.component.html',
@ -34,7 +35,6 @@ export class DossierAttributesListingScreenComponent extends ListingComponent<Do
@ViewChild('labelTemplate', { static: true }) labelTemplate: TemplateRef<never>;
@ViewChild('placeholderTemplate', { static: true }) placeholderTemplate: TemplateRef<never>;
@ViewChild('typeTemplate', { static: true }) typeTemplate: TemplateRef<never>;
protected readonly _primaryKey = 'label';
constructor(
protected readonly _injector: Injector,
@ -54,7 +54,7 @@ export class DossierAttributesListingScreenComponent extends ListingComponent<Do
await this._loadData();
}
openConfirmDeleteAttributeDialog($event: MouseEvent, dossierAttribute?: DossierAttributeConfig) {
openConfirmDeleteAttributeDialog($event: MouseEvent, dossierAttribute?: IDossierAttributeConfig) {
this._dialogService.openDialog('confirm', $event, null, async () => {
this._loadingService.start();
const ids = dossierAttribute ? [dossierAttribute.id] : this.entitiesService.selected.map(item => item.id);
@ -64,7 +64,7 @@ export class DossierAttributesListingScreenComponent extends ListingComponent<Do
});
}
openAddEditAttributeDialog($event: MouseEvent, dossierAttribute?: DossierAttributeConfig) {
openAddEditAttributeDialog($event: MouseEvent, dossierAttribute?: IDossierAttributeConfig) {
const dossierTemplateId = this._appStateService.activeDossierTemplateId;
this._dialogService.openDialog(

View File

@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component, forwardRef, Injector, OnInit, Templ
import { AppStateService } from '@state/app-state.service';
import { UserPreferenceService } from '@services/user-preference.service';
import { AdminDialogService } from '../../services/admin-dialog.service';
import { DossierTemplateModelWrapper } from '@models/file/dossier-template-model.wrapper';
import { DossierTemplate } from '@models/file/dossier-template';
import {
CircleButtonTypes,
DefaultListingServices,
@ -26,17 +26,16 @@ import { RouterHistoryService } from '@services/router-history.service';
{ provide: ListingComponent, useExisting: forwardRef(() => DossierTemplatesListingScreenComponent) }
]
})
export class DossierTemplatesListingScreenComponent extends ListingComponent<DossierTemplateModelWrapper> implements OnInit {
export class DossierTemplatesListingScreenComponent extends ListingComponent<DossierTemplate> implements OnInit {
readonly iconButtonTypes = IconButtonTypes;
readonly circleButtonTypes = CircleButtonTypes;
readonly currentUser = this._userService.currentUser;
readonly tableHeaderLabel = _('dossier-templates-listing.table-header.title');
tableColumnConfigs: TableColumnConfig<DossierTemplateModelWrapper>[];
@ViewChild('nameTemplate', { static: true }) nameTemplate: TemplateRef<never>;
@ViewChild('userTemplate', { static: true }) userTemplate: TemplateRef<never>;
@ViewChild('dateAddedTemplate', { static: true }) dateAddedTemplate: TemplateRef<never>;
@ViewChild('dateModifiedTemplate', { static: true }) dateModifiedTemplate: TemplateRef<never>;
protected readonly _primaryKey = 'name';
tableColumnConfigs: TableColumnConfig<DossierTemplate>[];
@ViewChild('nameTemplate', { static: true }) nameTemplate: TemplateRef<unknown>;
@ViewChild('userTemplate', { static: true }) userTemplate: TemplateRef<unknown>;
@ViewChild('dateAddedTemplate', { static: true }) dateAddedTemplate: TemplateRef<unknown>;
@ViewChild('dateModifiedTemplate', { static: true }) dateModifiedTemplate: TemplateRef<unknown>;
constructor(
protected readonly _injector: Injector,
@ -52,8 +51,6 @@ export class DossierTemplatesListingScreenComponent extends ListingComponent<Dos
super(_injector);
}
routerLinkFn = (dossierTemplate: DossierTemplateModelWrapper) => [dossierTemplate.dossierTemplateId, 'dictionaries'];
ngOnInit(): void {
this._configureTableColumns();
this.loadDossierTemplatesData();
@ -81,7 +78,7 @@ export class DossierTemplatesListingScreenComponent extends ListingComponent<Dos
this.tableColumnConfigs = [
{
label: _('dossier-templates-listing.table-col-names.name'),
sortByKey: 'name',
sortByKey: 'searchKey',
template: this.nameTemplate
},
{

View File

@ -9,7 +9,7 @@ import {
TemplateRef,
ViewChild
} from '@angular/core';
import { FileAttributeConfig, FileAttributesConfig, FileAttributesControllerService } from '@redaction/red-ui-http';
import { FileAttributesConfig, FileAttributesControllerService, IFileAttributeConfig } from '@redaction/red-ui-http';
import { AppStateService } from '@state/app-state.service';
import { ActivatedRoute } from '@angular/router';
import { AdminDialogService } from '../../services/admin-dialog.service';
@ -24,6 +24,7 @@ import {
import { fileAttributeTypesTranslations } from '../../translations/file-attribute-types-translations';
import { UserService } from '@services/user.service';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { FileAttributeConfig } from '@models/file/file-attribute-config';
@Component({
templateUrl: './file-attributes-listing-screen.component.html',
@ -41,14 +42,13 @@ export class FileAttributesListingScreenComponent extends ListingComponent<FileA
readonly translations = fileAttributeTypesTranslations;
readonly tableHeaderLabel = _('file-attributes-listing.table-header.title');
tableColumnConfigs: TableColumnConfig<FileAttributeConfig>[];
@ViewChild('labelTemplate', { static: true }) labelTemplate: TemplateRef<never>;
@ViewChild('typeTemplate', { static: true }) typeTemplate: TemplateRef<never>;
@ViewChild('readonlyTemplate', { static: true }) readonlyTemplate: TemplateRef<never>;
@ViewChild('csvColumnHeaderTemplate', { static: true }) csvColumnHeaderTemplate: TemplateRef<never>;
@ViewChild('filterableTemplate', { static: true }) filterableTemplate: TemplateRef<never>;
@ViewChild('displayedInFileListTemplate', { static: true }) displayedInFileListTemplate: TemplateRef<never>;
@ViewChild('primaryAttributeTemplate', { static: true }) primaryAttributeTemplate: TemplateRef<never>;
protected readonly _primaryKey = 'label';
@ViewChild('labelTemplate', { static: true }) labelTemplate: TemplateRef<unknown>;
@ViewChild('typeTemplate', { static: true }) typeTemplate: TemplateRef<unknown>;
@ViewChild('readonlyTemplate', { static: true }) readonlyTemplate: TemplateRef<unknown>;
@ViewChild('csvColumnHeaderTemplate', { static: true }) csvColumnHeaderTemplate: TemplateRef<unknown>;
@ViewChild('filterableTemplate', { static: true }) filterableTemplate: TemplateRef<unknown>;
@ViewChild('displayedInFileListTemplate', { static: true }) displayedInFileListTemplate: TemplateRef<unknown>;
@ViewChild('primaryAttributeTemplate', { static: true }) primaryAttributeTemplate: TemplateRef<unknown>;
private _existingConfiguration: FileAttributesConfig;
@ViewChild('fileInput') private _fileInput: ElementRef;
@ -70,7 +70,7 @@ export class FileAttributesListingScreenComponent extends ListingComponent<FileA
await this._loadData();
}
openAddEditAttributeDialog($event: MouseEvent, fileAttribute?: FileAttributeConfig) {
openAddEditAttributeDialog($event: MouseEvent, fileAttribute?: IFileAttributeConfig) {
this._dialogService.openDialog(
'addEditFileAttribute',
$event,
@ -86,7 +86,7 @@ export class FileAttributesListingScreenComponent extends ListingComponent<FileA
);
}
openConfirmDeleteAttributeDialog($event: MouseEvent, fileAttribute?: FileAttributeConfig) {
openConfirmDeleteAttributeDialog($event: MouseEvent, fileAttribute?: IFileAttributeConfig) {
this._dialogService.openDialog('deleteFileAttribute', $event, fileAttribute, async () => {
this._loadingService.start();
if (fileAttribute) {
@ -126,7 +126,7 @@ export class FileAttributesListingScreenComponent extends ListingComponent<FileA
this.tableColumnConfigs = [
{
label: _('file-attributes-listing.table-col-names.name'),
sortByKey: 'label',
sortByKey: 'searchKey',
width: '2fr',
template: this.labelTemplate
},
@ -170,7 +170,8 @@ export class FileAttributesListingScreenComponent extends ListingComponent<FileA
.getFileAttributesConfiguration(this._appStateService.activeDossierTemplateId)
.toPromise();
this._existingConfiguration = response;
this.entitiesService.setEntities(response?.fileAttributeConfigs || []);
const fileAttributeConfig = response?.fileAttributeConfigs.map(item => new FileAttributeConfig(item)) || [];
this.entitiesService.setEntities(fileAttributeConfig);
} catch (e) {}
this._loadingService.stop();

View File

@ -1,9 +1,9 @@
import { ChangeDetectionStrategy, Component, forwardRef, Injector, OnInit, TemplateRef, ViewChild } from '@angular/core';
import { Dossier } from '@redaction/red-ui-http';
import { IDossier } from '@redaction/red-ui-http';
import {
CircleButtonTypes,
DefaultListingServices,
Listable,
IListable,
ListingComponent,
LoadingService,
SortingOrders,
@ -20,7 +20,7 @@ import { distinctUntilChanged, map } from 'rxjs/operators';
import { getLeftDateTime } from '@utils/functions';
import { RouterHistoryService } from '@services/router-history.service';
interface DossierListItem extends Dossier, Listable {
interface DossierListItem extends IDossier, IListable {
readonly canRestore: boolean;
readonly restoreDate: string;
}
@ -29,11 +29,7 @@ interface DossierListItem extends Dossier, Listable {
templateUrl: './trash-screen.component.html',
styleUrls: ['./trash-screen.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [
...DefaultListingServices,
DossiersService,
{ provide: ListingComponent, useExisting: forwardRef(() => TrashScreenComponent) }
]
providers: [...DefaultListingServices, { provide: ListingComponent, useExisting: forwardRef(() => TrashScreenComponent) }]
})
export class TrashScreenComponent extends ListingComponent<DossierListItem> implements OnInit {
readonly circleButtonTypes = CircleButtonTypes;
@ -44,8 +40,6 @@ export class TrashScreenComponent extends ListingComponent<DossierListItem> impl
@ViewChild('ownerTemplate', { static: true }) ownerTemplate: TemplateRef<never>;
@ViewChild('deletedTimeTemplate', { static: true }) deletedTimeTemplate: TemplateRef<never>;
@ViewChild('restoreDateTemplate', { static: true }) restoreDateTemplate: TemplateRef<never>;
protected readonly _primaryKey = 'dossierName';
private readonly _deleteRetentionHours = this._configService.values.DELETE_RETENTION_HOURS;
constructor(
protected readonly _injector: Injector,
@ -78,7 +72,7 @@ export class TrashScreenComponent extends ListingComponent<DossierListItem> impl
this._loadingService.stop();
}
hardDelete(dossiers = this.entitiesService.selected) {
hardDelete(dossiers = this.entitiesService.selected): void {
const data = new ConfirmationDialogInput({
title: _('confirmation-dialog.delete-dossier.title'),
titleColor: TitleColors.PRIMARY,
@ -97,7 +91,7 @@ export class TrashScreenComponent extends ListingComponent<DossierListItem> impl
});
}
restore(dossiers = this.entitiesService.selected) {
restore(dossiers = this.entitiesService.selected): void {
this._loadingService.loadWhile(this._restore(dossiers));
}
@ -105,7 +99,7 @@ export class TrashScreenComponent extends ListingComponent<DossierListItem> impl
this.tableColumnConfigs = [
{
label: _('trash.table-col-names.name'),
sortByKey: 'dossierName',
sortByKey: 'searchKey',
template: this.filenameTemplate
},
{
@ -127,7 +121,7 @@ export class TrashScreenComponent extends ListingComponent<DossierListItem> impl
}
private _getRestoreDate(softDeletedTime: string): string {
return moment(softDeletedTime).add(this._deleteRetentionHours, 'hours').toISOString();
return moment(softDeletedTime).add(this._configService.values.DELETE_RETENTION_HOURS, 'hours').toISOString();
}
private async _loadDossiersData(): Promise<void> {
@ -140,15 +134,16 @@ export class TrashScreenComponent extends ListingComponent<DossierListItem> impl
return daysLeft >= 0 && hoursLeft >= 0 && minutesLeft > 0;
}
private _toListItems(dossiers: Dossier[]): DossierListItem[] {
private _toListItems(dossiers: IDossier[]): DossierListItem[] {
return dossiers.map(dossier => this._toListItem(dossier));
}
private _toListItem(dossier: Dossier): DossierListItem {
private _toListItem(dossier: IDossier): DossierListItem {
const restoreDate = this._getRestoreDate(dossier.softDeletedTime);
return {
id: dossier.dossierId,
...dossier,
searchKey: dossier.dossierName,
restoreDate,
canRestore: this._canRestoreDossier(restoreDate),
// Because of migrations, for some this is not set
@ -157,19 +152,19 @@ export class TrashScreenComponent extends ListingComponent<DossierListItem> impl
}
private async _restore(dossiers: DossierListItem[]): Promise<void> {
const dossierIds = dossiers.map(d => d.dossierId);
const dossierIds = dossiers.map(d => d.id);
await this._dossiersService.restore(dossierIds);
this._removeFromList(dossierIds);
}
private async _hardDelete(dossiers: DossierListItem[]) {
const dossierIds = dossiers.map(d => d.dossierId);
const dossierIds = dossiers.map(d => d.id);
await this._dossiersService.hardDelete(dossierIds);
this._removeFromList(dossierIds);
}
private _removeFromList(ids: string[]): void {
const entities = this.entitiesService.all.filter(e => !ids.includes(e.dossierId));
const entities = this.entitiesService.all.filter(e => !ids.includes(e.id));
this.entitiesService.setEntities(entities);
this.entitiesService.setSelected([]);
}

View File

@ -1,5 +1,5 @@
import { Component, forwardRef, Injector, OnInit, QueryList, TemplateRef, ViewChild, ViewChildren } from '@angular/core';
import { UserService, UserWrapper } from '@services/user.service';
import { UserService } from '@services/user.service';
import { UserControllerService } from '@redaction/red-ui-http';
import { AdminDialogService } from '../../services/admin-dialog.service';
import { TranslateService } from '@ngx-translate/core';
@ -18,27 +18,27 @@ import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { rolesTranslations } from '../../../../translations/roles-translations';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { User } from '@models/user';
@Component({
templateUrl: './user-listing-screen.component.html',
styleUrls: ['./user-listing-screen.component.scss'],
providers: [...DefaultListingServices, { provide: ListingComponent, useExisting: forwardRef(() => UserListingScreenComponent) }]
})
export class UserListingScreenComponent extends ListingComponent<UserWrapper> implements OnInit {
export class UserListingScreenComponent extends ListingComponent<User> implements OnInit {
readonly translations = rolesTranslations;
readonly iconButtonTypes = IconButtonTypes;
readonly circleButtonTypes = CircleButtonTypes;
readonly currentUser = this.userService.currentUser;
readonly canDeleteSelected$ = this._canDeleteSelected$;
readonly tableHeaderLabel = _('user-listing.table-header.title');
tableColumnConfigs: TableColumnConfig<UserWrapper>[];
tableColumnConfigs: TableColumnConfig<User>[];
collapsedDetails = false;
chartData: DoughnutChartConfig[] = [];
@ViewChild('nameTemplate', { static: true }) nameTemplate: TemplateRef<never>;
@ViewChild('emailTemplate', { static: true }) emailTemplate: TemplateRef<never>;
@ViewChild('activeTemplate', { static: true }) activeTemplate: TemplateRef<never>;
@ViewChild('rolesTemplate', { static: true }) rolesTemplate: TemplateRef<never>;
protected readonly _primaryKey = 'id';
@ViewChildren(InitialsAvatarComponent)
private readonly _avatars: QueryList<InitialsAvatarComponent>;
@ -62,22 +62,21 @@ export class UserListingScreenComponent extends ListingComponent<UserWrapper> im
async ngOnInit() {
this._configureTableColumns();
await this._loadData();
this.searchService.setSearchKey('searchKey');
}
openAddEditUserDialog($event: MouseEvent, user?: UserWrapper) {
openAddEditUserDialog($event: MouseEvent, user?: User) {
this._dialogService.openDialog('addEditUser', $event, user, async () => {
await this._loadData();
});
}
openDeleteUsersDialog(users: UserWrapper[], $event?: MouseEvent) {
openDeleteUsersDialog(users: User[], $event?: MouseEvent) {
this._dialogService.openDialog('deleteUsers', $event, users, async () => {
await this._loadData();
});
}
getDisplayRoles(user: UserWrapper) {
getDisplayRoles(user: User) {
const separator = ', ';
return (
user.roles.map(role => this._translateService.instant(this.translations[role])).join(separator) ||
@ -85,10 +84,10 @@ export class UserListingScreenComponent extends ListingComponent<UserWrapper> im
);
}
async toggleActive(user: UserWrapper) {
async toggleActive(user: User) {
this._loadingService.start();
user.roles = user.isActive ? [] : ['RED_USER'];
await this._userControllerService.updateProfile(user, user.id).toPromise();
const requestBody = { ...user, roles: user.isActive ? [] : ['RED_USER'] };
await this._userControllerService.updateProfile(requestBody, user.id).toPromise();
await this._loadData();
this._avatars.find(item => item.userId === user.id).detectChanges();
}

View File

@ -1,7 +1,7 @@
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { DossierAttributeConfig } from '@redaction/red-ui-http';
import { DossierAttributeConfigType } from '@redaction/red-ui-http';
export const dossierAttributeTypesTranslations: { [key in DossierAttributeConfig.TypeEnum]: string } = {
export const dossierAttributeTypesTranslations: { [key in DossierAttributeConfigType]: string } = {
TEXT: _('dossier-attribute-types.text'),
NUMBER: _('dossier-attribute-types.number'),
DATE: _('dossier-attribute-types.date'),

View File

@ -1,7 +1,7 @@
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { FileAttributeConfig } from '@redaction/red-ui-http';
import { FileAttributeConfigType } from '@redaction/red-ui-http';
export const fileAttributeTypesTranslations: { [key in FileAttributeConfig.TypeEnum]: string } = {
export const fileAttributeTypesTranslations: { [key in FileAttributeConfigType]: string } = {
TEXT: _('file-attribute-types.text'),
NUMBER: _('file-attribute-types.number'),
DATE: _('file-attribute-types.date')

View File

@ -2,7 +2,7 @@ import { Component, EventEmitter, Output } from '@angular/core';
import { AppStateService } from '@state/app-state.service';
import { FileManagementControllerService, ReanalysisControllerService } from '@redaction/red-ui-http';
import { PermissionsService } from '@services/permissions.service';
import { FileStatusWrapper } from '@models/file/file-status.wrapper';
import { File } from '@models/file/file';
import { FileActionService } from '../../services/file-action.service';
import { Observable } from 'rxjs';
import { DossiersDialogService } from '../../services/dossiers-dialog.service';
@ -19,8 +19,7 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
export class DossierOverviewBulkActionsComponent {
readonly circleButtonTypes = CircleButtonTypes;
@Output()
reload = new EventEmitter();
@Output() readonly reload = new EventEmitter();
constructor(
private readonly _appStateService: AppStateService,
@ -31,14 +30,14 @@ export class DossierOverviewBulkActionsComponent {
private readonly _fileActionService: FileActionService,
private readonly _loadingService: LoadingService,
private readonly _translateService: TranslateService,
private readonly _entitiesService: EntitiesService<FileStatusWrapper>
private readonly _entitiesService: EntitiesService<File>
) {}
get dossier() {
return this._appStateService?.activeDossier;
}
get selectedFiles(): FileStatusWrapper[] {
get selectedFiles(): File[] {
return this._entitiesService.selected;
}
@ -91,21 +90,18 @@ export class DossierOverviewBulkActionsComponent {
return this.selectedFiles.reduce((acc, file) => acc && file.canBeOCRed, true);
}
get fileStatuses() {
return this.selectedFiles.map(file => file.fileStatus.status);
get files() {
return this.selectedFiles.map(file => file.status);
}
// Under review
get canSetToUnderReview() {
return this.selectedFiles.reduce((acc, file) => acc && this._permissionsService.canSetUnderReview(file), true);
}
// Under approval
get canSetToUnderApproval() {
return this.selectedFiles.reduce((acc, file) => acc && this._permissionsService.canSetUnderApproval(file), true);
}
// Approve
get isReadyForApproval() {
return this.selectedFiles.reduce((acc, file) => acc && this._permissionsService.isReadyForApproval(file), true);
}
@ -114,7 +110,6 @@ export class DossierOverviewBulkActionsComponent {
return this.selectedFiles.reduce((acc, file) => acc && file.canBeApproved, true);
}
// Undo approval
get canUndoApproval() {
return this.selectedFiles.reduce((acc, file) => acc && this._permissionsService.canUndoApproval(file), true);
}
@ -156,7 +151,7 @@ export class DossierOverviewBulkActionsComponent {
this._assignFiles('approver', true);
} else {
this._performBulkAction(
this._fileActionService.setFileUnderApproval(this.selectedFiles, this._appStateService.activeDossier.approverIds[0])
this._fileActionService.setFilesUnderApproval(this.selectedFiles, this._appStateService.activeDossier.approverIds[0])
);
}
}
@ -164,16 +159,16 @@ export class DossierOverviewBulkActionsComponent {
async reanalyse() {
const fileIds = this.selectedFiles.filter(file => file.analysisRequired).map(file => file.fileId);
this._performBulkAction(
this._reanalysisControllerService.reanalyzeFilesForDossier(fileIds, this._appStateService.activeDossier.dossierId)
this._reanalysisControllerService.reanalyzeFilesForDossier(fileIds, this._appStateService.activeDossier.id)
);
}
ocr() {
this._performBulkAction(this._fileActionService.ocrFile(this.selectedFiles));
this._performBulkAction(this._fileActionService.ocrFiles(this.selectedFiles));
}
setToUnderReview() {
this._performBulkAction(this._fileActionService.setFileUnderReview(this.selectedFiles));
this._performBulkAction(this._fileActionService.setFilesUnderReview(this.selectedFiles));
}
approveDocuments() {
@ -187,11 +182,11 @@ export class DossierOverviewBulkActionsComponent {
question: _('confirmation-dialog.approve-multiple-files.question')
}),
() => {
this._performBulkAction(this._fileActionService.setFileApproved(this.selectedFiles));
this._performBulkAction(this._fileActionService.setFilesApproved(this.selectedFiles));
}
);
} else {
this._performBulkAction(this._fileActionService.setFileApproved(this.selectedFiles));
this._performBulkAction(this._fileActionService.setFilesApproved(this.selectedFiles));
}
}

View File

@ -3,7 +3,7 @@ import { FileAttributesConfig } from '@redaction/red-ui-http';
import { AppStateService } from '@state/app-state.service';
import { DossiersDialogService } from '../../services/dossiers-dialog.service';
import { AutoUnsubscribe } from '@iqser/common-ui';
import { FileStatusWrapper } from '@models/file/file-status.wrapper';
import { File } from '@models/file/file';
@Component({
selector: 'redaction-document-info',
@ -11,7 +11,7 @@ import { FileStatusWrapper } from '@models/file/file-status.wrapper';
styleUrls: ['./document-info.component.scss']
})
export class DocumentInfoComponent extends AutoUnsubscribe implements OnInit {
@Input() file: FileStatusWrapper;
@Input() file: File;
@Output() closeDocumentInfoView = new EventEmitter();
fileAttributesConfig: FileAttributesConfig;

View File

@ -4,7 +4,7 @@
</div>
<div>
<mat-icon svgIcon="red:user"></mat-icon>
<span>{{ 'dossier-overview.dossier-details.stats.people' | translate: { count: activeDossier.memberCount } }}</span>
<span>{{ 'dossier-overview.dossier-details.stats.people' | translate: { count: activeDossier.memberIds.length } }}</span>
</div>
<div>
<mat-icon svgIcon="red:pages"></mat-icon>

View File

@ -14,7 +14,7 @@
border-radius: 4px;
width: 100%;
justify-content: flex-start;
padding: 0 8px;
padding: 4px 8px;
margin-left: -8px;
&.link-property {

View File

@ -1,8 +1,8 @@
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { DossierAttributeWithValue } from '@models/dossier-attributes.model';
import { AppStateService } from '@state/app-state.service';
import { DossierWrapper } from '@state/model/dossier.wrapper';
import { DossierTemplateModel } from '@redaction/red-ui-http';
import { Dossier } from '@state/model/dossier';
import { IDossierTemplate } from '@redaction/red-ui-http';
import { DossiersDialogService } from '../../services/dossiers-dialog.service';
@Component({
@ -13,21 +13,21 @@ import { DossiersDialogService } from '../../services/dossiers-dialog.service';
export class DossierDetailsStatsComponent {
attributesExpanded = false;
@Input() dossierAttributes: DossierAttributeWithValue[];
@Output() openDossierDictionaryDialog = new EventEmitter();
@Output() readonly openDossierDictionaryDialog = new EventEmitter();
constructor(private readonly _appStateService: AppStateService, private readonly _dialogService: DossiersDialogService) {}
get activeDossier(): DossierWrapper {
get activeDossier(): Dossier {
return this._appStateService.activeDossier;
}
get dossierTemplate(): DossierTemplateModel {
get dossierTemplate(): IDossierTemplate {
return this._appStateService.getDossierTemplateById(this.activeDossier.dossierTemplateId);
}
openEditDossierAttributesDialog() {
this._dialogService.openDialog('editDossier', null, {
dossierWrapper: this.activeDossier,
dossier: this.activeDossier,
section: 'dossierAttributes'
});
}

View File

@ -50,7 +50,7 @@
<div *ngIf="hasFiles" class="mt-24 legend pb-32">
<div
(click)="filterService.toggleFilter('needsWorkFilters', filter.key)"
(click)="filterService.toggleFilter('needsWorkFilters', filter.id)"
*ngFor="let filter of needsWorkFilters$ | async"
[class.active]="filter.checked"
>

View File

@ -4,12 +4,13 @@ import { groupBy } from '@utils/functions';
import { DoughnutChartConfig } from '@shared/components/simple-doughnut-chart/simple-doughnut-chart.component';
import { TranslateChartService } from '@services/translate-chart.service';
import { StatusSorter } from '@utils/sorters/status-sorter';
import { UserService, UserWrapper } from '@services/user.service';
import { Toaster } from '@iqser/common-ui';
import { FilterService } from '@iqser/common-ui';
import { UserService } from '@services/user.service';
import { FilterService, Toaster } from '@iqser/common-ui';
import { DossierAttributeWithValue } from '@models/dossier-attributes.model';
import { fileStatusTranslations } from '../../translations/file-status-translations';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { List } from '@redaction/red-ui-http';
import { User } from '@models/user';
@Component({
selector: 'redaction-dossier-details',
@ -18,12 +19,12 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
})
export class DossierDetailsComponent implements OnInit {
documentsChartData: DoughnutChartConfig[] = [];
owner: UserWrapper;
owner: User;
editingOwner = false;
@Input() dossierAttributes: DossierAttributeWithValue[];
@Output() openAssignDossierMembersDialog = new EventEmitter();
@Output() openDossierDictionaryDialog = new EventEmitter();
@Output() toggleCollapse = new EventEmitter();
@Output() readonly openAssignDossierMembersDialog = new EventEmitter();
@Output() readonly openDossierDictionaryDialog = new EventEmitter();
@Output() readonly toggleCollapse = new EventEmitter();
collapseTooltip = _('dossier-details.collapse');
expandTooltip = _('dossier-details.expand');
@ -39,7 +40,7 @@ export class DossierDetailsComponent implements OnInit {
private readonly _toaster: Toaster
) {}
get memberIds(): string[] {
get memberIds(): List {
return this.appStateService.activeDossier.memberIds;
}
@ -66,12 +67,12 @@ export class DossierDetailsComponent implements OnInit {
const groups = groupBy(this.appStateService.activeDossier?.files, 'status');
this.documentsChartData = [];
for (const key of Object.keys(groups)) {
for (const status of Object.keys(groups)) {
this.documentsChartData.push({
value: groups[key].length,
color: key,
label: fileStatusTranslations[key],
key: key
value: groups[status].length,
color: status,
label: fileStatusTranslations[status],
key: status
});
}
this.documentsChartData.sort(StatusSorter.byStatus);
@ -79,7 +80,7 @@ export class DossierDetailsComponent implements OnInit {
this._changeDetectorRef.detectChanges();
}
async assignOwner(user: UserWrapper | string) {
async assignOwner(user: User | string) {
this.owner = typeof user === 'string' ? this._userService.getRedUserById(user) : user;
const dw = { ...this.appStateService.activeDossier, ownerId: this.owner.id };
await this.appStateService.createOrUpdateDossier(dw);

View File

@ -1,6 +1,6 @@
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
import { PermissionsService } from '@services/permissions.service';
import { DossierWrapper } from '@state/model/dossier.wrapper';
import { Dossier } from '../../../../state/model/dossier';
import { StatusSorter } from '@utils/sorters/status-sorter';
import { AppStateService } from '@state/app-state.service';
import { DossiersDialogService } from '../../services/dossiers-dialog.service';
@ -17,8 +17,8 @@ export class DossierListingActionsComponent {
readonly circleButtonTypes = CircleButtonTypes;
readonly currentUser = this._userService.currentUser;
@Input() dossier: DossierWrapper;
@Output() actionPerformed = new EventEmitter<DossierWrapper | undefined>();
@Input() dossier: Dossier;
@Output() readonly actionPerformed = new EventEmitter<Dossier | undefined>();
constructor(
readonly permissionsService: PermissionsService,
@ -27,14 +27,14 @@ export class DossierListingActionsComponent {
private readonly _userService: UserService
) {}
openEditDossierDialog($event: MouseEvent, dossierWrapper: DossierWrapper): void {
openEditDossierDialog($event: MouseEvent, dossier: Dossier): void {
this._dialogService.openDialog('editDossier', $event, {
dossierWrapper,
dossier,
afterSave: () => this.actionPerformed.emit()
});
}
reanalyseDossier($event: MouseEvent, dossier: DossierWrapper): void {
reanalyseDossier($event: MouseEvent, dossier: Dossier): void {
$event.stopPropagation();
this.appStateService.reanalyzeDossier(dossier).then(() => {
this.appStateService.loadAllDossiers().then(() => this.actionPerformed.emit());

View File

@ -1,17 +1,26 @@
<div *ngIf="screen === 'dossier-overview'" class="action-buttons">
<div *ngIf="isDossierOverviewList" class="action-buttons">
<ng-container *ngTemplateOutlet="actions"></ng-container>
<iqser-status-bar *ngIf="isWorkable" [configs]="statusBarConfig"></iqser-status-bar>
<iqser-status-bar *ngIf="showStatusBar" [configs]="statusBarConfig"></iqser-status-bar>
</div>
<ng-container *ngIf="screen === 'file-preview'">
<ng-container *ngIf="isFilePreview || isDossierOverviewWorkflow">
<ng-container *ngTemplateOutlet="actions"></ng-container>
</ng-container>
<ng-template #actions redactionLongPress (longPress)="forceReanalysisAction($event)">
<div class="file-actions" *ngIf="fileStatus">
<ng-template #actions (longPress)="forceReanalysisAction($event)" redactionLongPress>
<div *ngIf="file" class="file-actions">
<iqser-circle-button
(action)="openDocument()"
*ngIf="showOpenDocument"
[tooltipPosition]="tooltipPosition"
[tooltip]="'dossier-overview.open-document' | translate"
[type]="buttonType"
icon="red:collapse"
></iqser-circle-button>
<iqser-circle-button
(action)="openDeleteFileDialog($event)"
*ngIf="canDelete"
*ngIf="showDelete"
[tooltipPosition]="tooltipPosition"
[tooltip]="'dossier-overview.delete.action' | translate"
[type]="buttonType"
@ -20,7 +29,7 @@
<iqser-circle-button
(action)="assign($event)"
*ngIf="canAssign && screen === 'dossier-overview'"
*ngIf="showAssign"
[tooltipPosition]="tooltipPosition"
[tooltip]="assignTooltip | translate"
[type]="buttonType"
@ -29,7 +38,7 @@
<iqser-circle-button
(action)="assignToMe($event)"
*ngIf="canAssignToSelf && screen === 'dossier-overview'"
*ngIf="showAssignToSelf"
[tooltipPosition]="tooltipPosition"
[tooltip]="'dossier-overview.assign-me' | translate"
[type]="buttonType"
@ -39,7 +48,7 @@
<!-- download redacted file-->
<redaction-file-download-btn
[dossier]="appStateService.activeDossier"
[file]="fileStatus"
[file]="file"
[tooltipClass]="'small'"
[tooltipPosition]="tooltipPosition"
[type]="buttonType"
@ -47,7 +56,7 @@
<iqser-circle-button
(action)="toggleViewDocumentInfo()"
*ngIf="screen === 'file-preview'"
*ngIf="showDocumentInfo"
[attr.aria-expanded]="activeDocumentInfo"
[tooltip]="'file-preview.document-info' | translate"
icon="red:status-info"
@ -56,9 +65,9 @@
<iqser-circle-button
(action)="toggleExcludePages()"
*ngIf="screen === 'file-preview'"
*ngIf="showExcludePages"
[attr.aria-expanded]="activeExcludePages"
[showDot]="!!fileStatus.excludedPages?.length"
[showDot]="!!file.excludedPages?.length"
[tooltip]="'file-preview.exclude-pages' | translate"
icon="red:exclude-pages"
tooltipPosition="below"
@ -67,7 +76,7 @@
<!-- Ready for approval-->
<iqser-circle-button
(action)="setFileUnderApproval($event)"
*ngIf="canSetToUnderApproval"
*ngIf="showUnderApproval"
[tooltipPosition]="tooltipPosition"
[tooltip]="'dossier-overview.under-approval' | translate"
[type]="buttonType"
@ -77,7 +86,7 @@
<!-- Back to review -->
<iqser-circle-button
(action)="setFileUnderReview($event, true)"
*ngIf="canSetToUnderReview"
*ngIf="showUnderReview"
[tooltipPosition]="tooltipPosition"
[tooltip]="'dossier-overview.under-review' | translate"
[type]="buttonType"
@ -87,12 +96,10 @@
<!-- Approved-->
<iqser-circle-button
(action)="setFileApproved($event)"
*ngIf="readyForApproval"
[disabled]="!fileStatus.canBeApproved"
*ngIf="showApprove"
[disabled]="!file.canBeApproved"
[tooltipPosition]="tooltipPosition"
[tooltip]="
fileStatus.canBeApproved ? ('dossier-overview.approve' | translate) : ('dossier-overview.approve-disabled' | translate)
"
[tooltip]="file.canBeApproved ? ('dossier-overview.approve' | translate) : ('dossier-overview.approve-disabled' | translate)"
[type]="buttonType"
icon="red:approved"
></iqser-circle-button>
@ -100,7 +107,7 @@
<!-- Back to approval -->
<iqser-circle-button
(action)="setFileUnderApproval($event)"
*ngIf="canUndoApproval"
*ngIf="showUndoApproval"
[tooltipPosition]="tooltipPosition"
[tooltip]="'dossier-overview.under-approval' | translate"
[type]="buttonType"
@ -109,7 +116,7 @@
<iqser-circle-button
(action)="ocrFile($event)"
*ngIf="fileStatus.canBeOCRed"
*ngIf="showOCR"
[tooltipPosition]="tooltipPosition"
[tooltip]="'dossier-overview.ocr-file' | translate"
[type]="buttonType"
@ -119,30 +126,30 @@
<!-- reanalyse file preview -->
<iqser-circle-button
(action)="reanalyseFile($event)"
*ngIf="canReanalyse && screen === 'file-preview'"
*ngIf="canReanalyse && isFilePreview"
[tooltip]="'file-preview.reanalyse-notification' | translate"
[type]="circleButtonTypes.warn"
icon="iqser:refresh"
tooltipClass="warn small"
tooltipPosition="below"
[type]="circleButtonTypes.warn"
></iqser-circle-button>
<!-- reanalyse file listing -->
<iqser-circle-button
(action)="reanalyseFile($event)"
*ngIf="canReanalyse && screen === 'dossier-overview'"
*ngIf="canReanalyse && isDossierOverview"
[tooltipPosition]="tooltipPosition"
[tooltip]="'dossier-overview.reanalyse.action' | translate"
icon="iqser:refresh"
[type]="circleButtonTypes.dark"
icon="iqser:refresh"
></iqser-circle-button>
<!-- exclude from redaction -->
<div class="iqser-input-group">
<mat-slide-toggle
(change)="toggleAnalysis()"
(click)="$event.stopPropagation()"
[checked]="!fileStatus?.excluded"
[class.mr-24]="screen === 'dossier-overview'"
[checked]="!file?.excluded"
[class.mr-24]="isDossierOverviewList"
[disabled]="!canToggleAnalysis"
[matTooltipPosition]="tooltipPosition"
[matTooltip]="toggleTooltip | translate"

View File

@ -1,5 +1,11 @@
@use 'common-mixins';
@use 'variables';
.file-actions {
display: flex;
overflow-y: auto;
color: variables.$grey-1;
@include common-mixins.no-scroll-bar;
> *:not(:last-child) {
margin-right: 2px;

View File

@ -1,17 +1,17 @@
import { Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, SimpleChanges } from '@angular/core';
import { PermissionsService } from '@services/permissions.service';
import { FileStatusWrapper } from '@models/file/file-status.wrapper';
import { File } from '@models/file/file';
import { AppStateService } from '@state/app-state.service';
import { FileActionService } from '../../services/file-action.service';
import { DossiersDialogService } from '../../services/dossiers-dialog.service';
import { ConfirmationDialogInput } from '@shared/dialogs/confirmation-dialog/confirmation-dialog.component';
import { AutoUnsubscribe, CircleButtonType, CircleButtonTypes, LoadingService, StatusBarConfig, Toaster } from '@iqser/common-ui';
import { AutoUnsubscribe, CircleButtonType, CircleButtonTypes, LoadingService, Required, StatusBarConfig, Toaster } from '@iqser/common-ui';
import { FileManagementControllerService, FileStatus } from '@redaction/red-ui-http';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { UserService } from '@services/user.service';
import { filter } from 'rxjs/operators';
import { UserPreferenceService } from '../../../../services/user-preference.service';
import { LongPressEvent } from '../../../shared/directives/long-press.directive';
import { UserPreferenceService } from '@services/user-preference.service';
import { LongPressEvent } from '@shared/directives/long-press.directive';
@Component({
selector: 'redaction-file-actions',
@ -22,28 +22,32 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnInit, OnD
readonly circleButtonTypes = CircleButtonTypes;
readonly currentUser = this._userService.currentUser;
@Input() fileStatus: FileStatusWrapper;
@Input() file: File;
@Input() activeDocumentInfo: boolean;
@Input() activeExcludePages: boolean;
@Output() actionPerformed = new EventEmitter<string>();
@Input() @Required() type: 'file-preview' | 'dossier-overview-list' | 'dossier-overview-workflow';
@Output() readonly actionPerformed = new EventEmitter<string>();
screen: 'file-preview' | 'dossier-overview';
statusBarConfig?: readonly StatusBarConfig<FileStatus.StatusEnum>[];
statusBarConfig?: readonly StatusBarConfig<FileStatus>[];
tooltipPosition?: 'below' | 'above';
toggleTooltip?: string;
assignTooltip?: string;
buttonType?: CircleButtonType;
isWorkable: boolean;
canUndoApproval: boolean;
canAssignToSelf: boolean;
canAssign: boolean;
canDelete: boolean;
showUndoApproval: boolean;
showAssignToSelf: boolean;
showAssign: boolean;
showDelete: boolean;
showOCR: boolean;
canReanalyse: boolean;
canSetToUnderReview: boolean;
canSetToUnderApproval: boolean;
readyForApproval: boolean;
showUnderReview: boolean;
showUnderApproval: boolean;
showApprove: boolean;
canToggleAnalysis: boolean;
showExcludePages: boolean;
showDocumentInfo: boolean;
showStatusBar: boolean;
showOpenDocument: boolean;
constructor(
readonly permissionsService: PermissionsService,
@ -59,29 +63,39 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnInit, OnD
super();
}
get isDossierOverviewList(): boolean {
return this.type === 'dossier-overview-list';
}
get isDossierOverviewWorkflow(): boolean {
return this.type === 'dossier-overview-workflow';
}
get isFilePreview(): boolean {
return this.type === 'file-preview';
}
get isDossierOverview(): boolean {
return this.type.startsWith('dossier-overview-list');
}
private get _toggleTooltip(): string {
if (!this.currentUser.isManager) {
return _('file-preview.toggle-analysis.only-managers');
}
return this.fileStatus?.excluded ? _('file-preview.toggle-analysis.enable') : _('file-preview.toggle-analysis.disable');
return this.file?.excluded ? _('file-preview.toggle-analysis.enable') : _('file-preview.toggle-analysis.disable');
}
ngOnInit(): void {
if (this.fileStatus) {
this.screen = 'dossier-overview';
} else {
this.fileStatus = this.appStateService.activeFile;
this.screen = 'file-preview';
if (!this.file) {
this.file = this.appStateService.activeFile;
}
this._setup();
this.addSubscription = this.appStateService.fileChanged$
.pipe(filter(file => file.fileId === this.fileStatus?.fileId))
.subscribe(fileStatus => {
this.fileStatus = fileStatus;
this._setup();
});
this.addSubscription = this.appStateService.fileChanged$.pipe(filter(file => file.fileId === this.file?.fileId)).subscribe(file => {
this.file = file;
this._setup();
});
this.addSubscription = this.appStateService.dossierChanged$.subscribe(() => {
this._setup();
@ -96,6 +110,10 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnInit, OnD
this.actionPerformed.emit('view-exclude-pages');
}
openDocument() {
this.actionPerformed.emit('navigate');
}
openDeleteFileDialog($event: MouseEvent) {
this._dialogService.openDialog(
'confirm',
@ -107,7 +125,7 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnInit, OnD
async () => {
this._loadingService.start();
await this._fileManagementControllerService
.deleteFiles([this.fileStatus.fileId], this.fileStatus.dossierId)
.deleteFiles([this.file.fileId], this.file.dossierId)
.toPromise()
.catch(error => {
this._toaster.error(_('error.http.generic'), { params: error });
@ -120,8 +138,8 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnInit, OnD
}
assign($event: MouseEvent) {
const mode = this.fileStatus.isUnderApproval ? 'approver' : 'reviewer';
const files = [this.fileStatus];
const mode = this.file.isUnderApproval ? 'approver' : 'reviewer';
const files = [this.file];
this._dialogService.openDialog('assignFile', $event, { mode, files }, () => {
this.actionPerformed.emit('assign-reviewer');
});
@ -130,7 +148,7 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnInit, OnD
async assignToMe($event: MouseEvent) {
$event.stopPropagation();
await this._fileActionService.assignToMe(this.fileStatus, () => {
await this._fileActionService.assignToMe([this.file], () => {
this.reloadDossiers('reanalyse');
});
}
@ -139,7 +157,7 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnInit, OnD
if ($event) {
$event.stopPropagation();
}
this.addSubscription = this._fileActionService.reanalyseFile(this.fileStatus).subscribe(() => {
this.addSubscription = this._fileActionService.reanalyseFile(this.file).subscribe(() => {
this.reloadDossiers('reanalyse');
});
}
@ -147,9 +165,9 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnInit, OnD
setFileUnderApproval($event: MouseEvent) {
$event.stopPropagation();
if (this.appStateService.activeDossier.approverIds.length > 1) {
this._fileActionService.assignFile('approver', $event, this.fileStatus, () => this.reloadDossiers('assign-reviewer'), true);
this._fileActionService.assignFile('approver', $event, this.file, () => this.reloadDossiers('assign-reviewer'), true);
} else {
this.addSubscription = this._fileActionService.setFileUnderApproval(this.fileStatus).subscribe(() => {
this.addSubscription = this._fileActionService.setFilesUnderApproval([this.file]).subscribe(() => {
this.reloadDossiers('set-under-approval');
});
}
@ -157,7 +175,7 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnInit, OnD
setFileApproved($event: MouseEvent) {
$event.stopPropagation();
if (this.fileStatus.hasUpdates) {
if (this.file.hasUpdates) {
this._dialogService.openDialog(
'confirm',
$event,
@ -176,7 +194,7 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnInit, OnD
ocrFile($event: MouseEvent) {
$event.stopPropagation();
this.addSubscription = this._fileActionService.ocrFile(this.fileStatus).subscribe(() => {
this.addSubscription = this._fileActionService.ocrFiles([this.file]).subscribe(() => {
this.reloadDossiers('ocr-file');
});
}
@ -185,7 +203,7 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnInit, OnD
this._fileActionService.assignFile(
'reviewer',
$event,
this.fileStatus,
this.file,
() => this.reloadDossiers('assign-reviewer'),
ignoreDialogChanges
);
@ -198,47 +216,54 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnInit, OnD
}
async toggleAnalysis() {
await this._fileActionService.toggleAnalysis(this.fileStatus).toPromise();
await this._fileActionService.toggleAnalysis(this.file).toPromise();
await this.appStateService.getFiles();
this.actionPerformed.emit(this.fileStatus?.excluded ? 'enable-analysis' : 'disable-analysis');
this.actionPerformed.emit(this.file?.excluded ? 'enable-analysis' : 'disable-analysis');
}
ngOnChanges(changes: SimpleChanges): void {
if (changes.fileStatus) {
if (changes.file) {
this._setup();
}
}
forceReanalysisAction($event: LongPressEvent) {
if (this._userPreferenceService.areDevFeaturesEnabled) {
this.canReanalyse = $event.touchEnd ? this.permissionsService.canReanalyseFile(this.fileStatus) : true;
this.canReanalyse = $event.touchEnd ? this.permissionsService.canReanalyseFile(this.file) : true;
}
}
private _setFileApproved() {
this.addSubscription = this._fileActionService.setFileApproved(this.fileStatus).subscribe(() => {
this.addSubscription = this._fileActionService.setFilesApproved([this.file]).subscribe(() => {
this.reloadDossiers('set-approved');
});
}
private _setup() {
this.statusBarConfig = [{ color: this.fileStatus.status, length: 1 }];
this.tooltipPosition = this.screen === 'file-preview' ? 'below' : 'above';
this.assignTooltip = this.fileStatus.isUnderApproval
? _('dossier-overview.assign-approver')
: _('dossier-overview.assign-reviewer');
this.buttonType = this.screen === 'file-preview' ? CircleButtonTypes.default : CircleButtonTypes.dark;
this.isWorkable = this.fileStatus.isWorkable;
this.statusBarConfig = [{ color: this.file.status, length: 1 }];
this.tooltipPosition = this.isFilePreview ? 'below' : 'above';
this.assignTooltip = this.file.isUnderApproval ? _('dossier-overview.assign-approver') : _('dossier-overview.assign-reviewer');
this.buttonType = this.isFilePreview ? CircleButtonTypes.default : CircleButtonTypes.dark;
this.toggleTooltip = this._toggleTooltip;
this.canUndoApproval = this.permissionsService.canUndoApproval(this.fileStatus);
this.canAssignToSelf = this.permissionsService.canAssignToSelf(this.fileStatus);
this.canAssign = this.permissionsService.canAssignUser(this.fileStatus);
this.canDelete = this.permissionsService.canDeleteFile(this.fileStatus);
this.canReanalyse = this.permissionsService.canReanalyseFile(this.fileStatus);
this.canSetToUnderReview = this.permissionsService.canSetUnderReview(this.fileStatus);
this.canSetToUnderApproval = this.permissionsService.canSetUnderApproval(this.fileStatus);
this.readyForApproval = this.permissionsService.isReadyForApproval(this.fileStatus);
this.canToggleAnalysis = this.permissionsService.canToggleAnalysis(this.fileStatus);
this.showUndoApproval = this.permissionsService.canUndoApproval(this.file) && !this.isDossierOverviewWorkflow;
this.showUnderReview = this.permissionsService.canSetUnderReview(this.file) && !this.isDossierOverviewWorkflow;
this.showUnderApproval = this.permissionsService.canSetUnderApproval(this.file) && !this.isDossierOverviewWorkflow;
this.showApprove = this.permissionsService.isReadyForApproval(this.file) && !this.isDossierOverviewWorkflow;
this.canToggleAnalysis = this.permissionsService.canToggleAnalysis(this.file);
this.showDelete = this.permissionsService.canDeleteFile(this.file);
this.showOCR = this.file.canBeOCRed;
this.canReanalyse = this.permissionsService.canReanalyseFile(this.file);
this.showStatusBar = this.file.isWorkable && this.isDossierOverviewList;
this.showAssignToSelf = this.permissionsService.canAssignToSelf(this.file) && this.isDossierOverview;
this.showAssign = this.permissionsService.canAssignUser(this.file) && this.isDossierOverview;
this.showOpenDocument = this.file.canBeOpened && this.isDossierOverviewWorkflow;
this.showExcludePages = this.isFilePreview;
this.showDocumentInfo = this.isFilePreview;
}
}

View File

@ -93,7 +93,7 @@
</div>
<div
(click)="scrollQuickNavLast()"
[class.disabled]="activeViewerPage === fileData?.fileStatus?.numberOfPages"
[class.disabled]="activeViewerPage === fileData?.file?.numberOfPages"
[matTooltip]="'file-preview.quick-nav.jump-last' | translate"
class="jump"
matTooltipPosition="above"
@ -141,7 +141,7 @@
[verticalPadding]="40"
icon="red:document"
>
<ng-container *ngIf="fileData?.fileStatus?.excludedPages?.includes(activeViewerPage)">
<ng-container *ngIf="fileData?.file?.excludedPages?.includes(activeViewerPage)">
{{ 'file-preview.tabs.annotations.page-is' | translate }}
<a
(click)="actionPerformed.emit('view-exclude-pages')"
@ -171,15 +171,15 @@
</ng-container>
<redaction-annotations-list
[canMultiSelect]="!isReadOnly"
[annotations]="(displayedAnnotations$ | async)?.get(activeViewerPage)"
[selectedAnnotations]="selectedAnnotations"
[annotationActionsTemplate]="annotationActionsTemplate"
[(multiSelectActive)]="multiSelectActive"
[activeViewerPage]="activeViewerPage"
(deselectAnnotations)="deselectAnnotations.emit($event)"
(pagesPanelActive)="pagesPanelActive = $event"
(selectAnnotations)="selectAnnotations.emit($event)"
(deselectAnnotations)="deselectAnnotations.emit($event)"
[(multiSelectActive)]="multiSelectActive"
[activeViewerPage]="activeViewerPage"
[annotationActionsTemplate]="annotationActionsTemplate"
[annotations]="(displayedAnnotations$ | async)?.get(activeViewerPage)"
[canMultiSelect]="!isReadOnly"
[selectedAnnotations]="selectedAnnotations"
></redaction-annotations-list>
</div>
</ng-container>
@ -187,7 +187,7 @@
<redaction-page-exclusion
(actionPerformed)="actionPerformed.emit($event)"
*ngIf="excludePages"
[fileStatus]="fileData.fileStatus"
[file]="fileData.file"
></redaction-page-exclusion>
</div>
</div>

View File

@ -3,7 +3,7 @@ import { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { AnnotationProcessingService } from '../../services/annotation-processing.service';
import { MatDialogRef, MatDialogState } from '@angular/material/dialog';
import scrollIntoView from 'scroll-into-view-if-needed';
import { CircleButtonTypes, Debounce, FilterService, IconButtonTypes, IqserEventTarget, NestedFilter } from '@iqser/common-ui';
import { CircleButtonTypes, Debounce, FilterService, IconButtonTypes, INestedFilter, IqserEventTarget } from '@iqser/common-ui';
import { FileDataModel } from '@models/file/file-data.model';
import { PermissionsService } from '@services/permissions.service';
import { WebViewerInstance } from '@pdftron/webviewer';
@ -43,8 +43,10 @@ export class FileWorkloadComponent {
@Output() readonly actionPerformed = new EventEmitter<string>();
displayedPages: number[] = [];
pagesPanelActive = true;
readonly displayedAnnotations$ = this._displayedAnnotations$;
@ViewChild('annotationsElement') private readonly _annotationsElement: ElementRef;
@ViewChild('quickNavigation') private readonly _quickNavigationElement: ElementRef;
private _annotations$ = new BehaviorSubject<AnnotationWrapper[]>([]);
constructor(
private readonly _permissionsService: PermissionsService,
@ -53,9 +55,6 @@ export class FileWorkloadComponent {
private readonly _annotationProcessingService: AnnotationProcessingService
) {}
private _annotations$ = new BehaviorSubject<AnnotationWrapper[]>([]);
readonly displayedAnnotations$ = this._displayedAnnotations$;
@Input()
set annotations(value: AnnotationWrapper[]) {
this._annotations$.next(value);
@ -78,7 +77,7 @@ export class FileWorkloadComponent {
}
get isProcessing(): boolean {
return this.fileData?.fileStatus?.isProcessing;
return this.fileData?.file?.isProcessing;
}
get activeAnnotations(): AnnotationWrapper[] | undefined {
@ -93,6 +92,14 @@ export class FileWorkloadComponent {
return this.selectedAnnotations?.length ? this.selectedAnnotations[0] : null;
}
private get _displayedAnnotations$(): Observable<Map<number, AnnotationWrapper[]>> {
const primary$ = this._filterService.getFilterModels$('primaryFilters');
const secondary$ = this._filterService.getFilterModels$('secondaryFilters');
return combineLatest([this._annotations$, primary$, secondary$]).pipe(
map(([annotations, primary, secondary]) => this._filterAnnotations(annotations, primary, secondary))
);
}
private static _scrollToFirstElement(elements: HTMLElement[], mode: 'always' | 'if-needed' = 'if-needed') {
if (elements.length > 0) {
scrollIntoView(elements[0], {
@ -120,19 +127,6 @@ export class FileWorkloadComponent {
this.deselectAnnotations.emit(this.activeAnnotations);
}
private _filterAnnotations(
annotations: AnnotationWrapper[],
primary: NestedFilter[],
secondary: NestedFilter[] = []
): Map<number, AnnotationWrapper[]> {
if (!primary) {
return;
}
this.displayedAnnotations = this._annotationProcessingService.filterAndGroupAnnotations(annotations, primary, secondary);
this.displayedPages = [...this.displayedAnnotations.keys()];
return this.displayedAnnotations;
}
@HostListener('window:keyup', ['$event'])
handleKeyEvent($event: KeyboardEvent): void {
if (
@ -210,7 +204,7 @@ export class FileWorkloadComponent {
}
scrollQuickNavLast(): void {
this.selectPage.emit(this.fileData.fileStatus.numberOfPages);
this.selectPage.emit(this.fileData.file.numberOfPages);
}
pageSelectedByClick($event: number): void {
@ -232,12 +226,17 @@ export class FileWorkloadComponent {
this.selectPage.emit(this._nextPageWithAnnotations());
}
private get _displayedAnnotations$(): Observable<Map<number, AnnotationWrapper[]>> {
const primary$ = this._filterService.getFilterModels$('primaryFilters');
const secondary$ = this._filterService.getFilterModels$('secondaryFilters');
return combineLatest([this._annotations$, primary$, secondary$]).pipe(
map(([annotations, primary, secondary]) => this._filterAnnotations(annotations, primary, secondary))
);
private _filterAnnotations(
annotations: AnnotationWrapper[],
primary: INestedFilter[],
secondary: INestedFilter[] = []
): Map<number, AnnotationWrapper[]> {
if (!primary) {
return;
}
this.displayedAnnotations = this._annotationProcessingService.filterAndGroupAnnotations(annotations, primary, secondary);
this.displayedPages = [...this.displayedAnnotations.keys()];
return this.displayedAnnotations;
}
private _selectFirstAnnotationOnCurrentPageIfNecessary() {

View File

@ -1,7 +1,7 @@
import { Component, Input } from '@angular/core';
import { AppStateService } from '@state/app-state.service';
import { FileStatusWrapper } from '@models/file/file-status.wrapper';
import { DossierWrapper } from '@state/model/dossier.wrapper';
import { File } from '@models/file/file';
import { Dossier } from '../../../../state/model/dossier';
@Component({
selector: 'redaction-needs-work-badge',
@ -9,7 +9,7 @@ import { DossierWrapper } from '@state/model/dossier.wrapper';
styleUrls: ['./needs-work-badge.component.scss']
})
export class NeedsWorkBadgeComponent {
@Input() needsWorkInput: FileStatusWrapper | DossierWrapper;
@Input() needsWorkInput: File | Dossier;
constructor(private readonly _appStateService: AppStateService) {}
@ -38,19 +38,19 @@ export class NeedsWorkBadgeComponent {
}
get hasImages() {
return this.needsWorkInput instanceof FileStatusWrapper && this.needsWorkInput.hasImages;
return this.needsWorkInput instanceof File && this.needsWorkInput.hasImages;
}
get hasUpdates() {
return this.needsWorkInput instanceof FileStatusWrapper && this.needsWorkInput.hasUpdates;
return this.needsWorkInput instanceof File && this.needsWorkInput.hasUpdates;
}
get hasAnnotationComments(): boolean {
return this.needsWorkInput instanceof FileStatusWrapper && (<any>this.needsWorkInput).hasAnnotationComments;
return this.needsWorkInput instanceof File && (<any>this.needsWorkInput).hasAnnotationComments;
}
reanalysisRequired() {
if (this.needsWorkInput instanceof DossierWrapper) {
if (this.needsWorkInput instanceof Dossier) {
return this.needsWorkInput.reanalysisRequired;
} else {
return this.needsWorkInput.analysisRequired;

View File

@ -1,9 +1,9 @@
import { Component, EventEmitter, Input, OnChanges, Output, ViewChild } from '@angular/core';
import { PermissionsService } from '@services/permissions.service';
import { PageRange, ReanalysisControllerService } from '@redaction/red-ui-http';
import { InputWithActionComponent, Toaster, LoadingService } from '@iqser/common-ui';
import { InputWithActionComponent, LoadingService, Toaster } from '@iqser/common-ui';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { FileStatusWrapper } from '@models/file/file-status.wrapper';
import { File } from '@models/file/file';
@Component({
selector: 'redaction-page-exclusion',
@ -11,7 +11,7 @@ import { FileStatusWrapper } from '@models/file/file-status.wrapper';
styleUrls: ['./page-exclusion.component.scss']
})
export class PageExclusionComponent implements OnChanges {
@Input() fileStatus: FileStatusWrapper;
@Input() file: File;
@Output() readonly actionPerformed = new EventEmitter<string>();
excludedPagesRanges: PageRange[] = [];
@ -25,7 +25,7 @@ export class PageExclusionComponent implements OnChanges {
) {}
ngOnChanges(): void {
const excludedPages = (this.fileStatus?.excludedPages || []).sort((p1, p2) => p1 - p2);
const excludedPages = (this.file?.excludedPages || []).sort((p1, p2) => p1 - p2);
this.excludedPagesRanges = excludedPages.reduce((ranges, page) => {
if (!ranges.length) {
return [{ startPage: page, endPage: page }];
@ -60,8 +60,8 @@ export class PageExclusionComponent implements OnChanges {
{
pageRanges: pageRanges
},
this.fileStatus.dossierId,
this.fileStatus.fileId
this.file.dossierId,
this.file.fileId
)
.toPromise();
this._inputComponent.reset();
@ -79,8 +79,8 @@ export class PageExclusionComponent implements OnChanges {
{
pageRanges: [range]
},
this.fileStatus.dossierId,
this.fileStatus.fileId
this.file.dossierId,
this.file.fileId
)
.toPromise();
this._inputComponent.reset();

View File

@ -1,5 +1,5 @@
<div class="page">
<div #viewer [id]="fileStatus.fileId" class="viewer"></div>
<div #viewer [id]="file.fileId" class="viewer"></div>
</div>
<input #compareFileInput (change)="uploadFile($event.target['files'])" class="file-upload-input" type="file" />

View File

@ -17,7 +17,7 @@ import { TranslateService } from '@ngx-translate/core';
import { ManualRedactionEntryWrapper } from '@models/file/manual-redaction-entry.wrapper';
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { ManualAnnotationService } from '../../services/manual-annotation.service';
import { FileStatusWrapper } from '@models/file/file-status.wrapper';
import { File } from '@models/file/file';
import { environment } from '@environments/environment';
import { AnnotationDrawService } from '../../services/annotation-draw.service';
import { AnnotationActionsService } from '../../services/annotation-actions.service';
@ -43,18 +43,18 @@ import Annotation = Core.Annotations.Annotation;
})
export class PdfViewerComponent implements OnInit, OnChanges {
@Input() fileData: Blob;
@Input() fileStatus: FileStatusWrapper;
@Input() file: File;
@Input() canPerformActions = false;
@Input() annotations: AnnotationWrapper[];
@Input() shouldDeselectAnnotationsOnPageChange = true;
@Input() multiSelectActive: boolean;
@Output() fileReady = new EventEmitter();
@Output() annotationSelected = new EventEmitter<string[]>();
@Output() manualAnnotationRequested = new EventEmitter<ManualRedactionEntryWrapper>();
@Output() pageChanged = new EventEmitter<number>();
@Output() keyUp = new EventEmitter<KeyboardEvent>();
@Output() viewerReady = new EventEmitter<WebViewerInstance>();
@Output() annotationsChanged = new EventEmitter<AnnotationWrapper>();
@Output() readonly fileReady = new EventEmitter();
@Output() readonly annotationSelected = new EventEmitter<string[]>();
@Output() readonly manualAnnotationRequested = new EventEmitter<ManualRedactionEntryWrapper>();
@Output() readonly pageChanged = new EventEmitter<number>();
@Output() readonly keyUp = new EventEmitter<KeyboardEvent>();
@Output() readonly viewerReady = new EventEmitter<WebViewerInstance>();
@Output() readonly annotationsChanged = new EventEmitter<AnnotationWrapper>();
@ViewChild('viewer', { static: true }) viewer: ElementRef;
@ViewChild('compareFileInput', { static: true }) compareFileInput: ElementRef;
instance: WebViewerInstance;
@ -146,7 +146,7 @@ export class PdfViewerComponent implements OnInit, OnChanges {
compareDocument,
mergedDocument,
this.instance,
this.fileStatus,
this.file,
() => {
this.viewMode = 'COMPARE';
},
@ -188,7 +188,7 @@ export class PdfViewerComponent implements OnInit, OnChanges {
await pdfNet.initialize(environment.licenseKey ? atob(environment.licenseKey) : null);
const currentDocument = await pdfNet.PDFDoc.createFromBuffer(await this.fileData.arrayBuffer());
this.instance.UI.loadDocument(currentDocument, {
filename: this.fileStatus ? this.fileStatus.filename : 'document.pdf'
filename: this.file ? this.file.filename : 'document.pdf'
});
this.instance.UI.disableElements(['closeCompareButton']);
this.instance.UI.enableElements(['compareButton']);
@ -571,7 +571,7 @@ export class PdfViewerComponent implements OnInit, OnChanges {
private _loadDocument() {
if (this.fileData) {
this.instance.UI.loadDocument(this.fileData, {
filename: this.fileStatus ? this.fileStatus.filename : 'document.pdf'
filename: this.file ? this.file.filename : 'document.pdf'
});
}
}

View File

@ -1,10 +1,10 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { Dossier } from '@redaction/red-ui-http';
import { IDossier } from '@redaction/red-ui-http';
import { AppStateService } from '@state/app-state.service';
import { UserService } from '@services/user.service';
import { Toaster } from '@iqser/common-ui';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { DossierWrapper } from '@state/model/dossier.wrapper';
import { Dossier } from '../../../../state/model/dossier';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
@Component({
@ -16,9 +16,9 @@ export class TeamMembersManagerComponent implements OnInit {
teamForm: FormGroup;
searchQuery = '';
@Input() dossierWrapper: DossierWrapper;
@Input() dossier: Dossier;
@Output() readonly save = new EventEmitter<IDossier>();
@Output() readonly save = new EventEmitter<Dossier>();
readonly ownersSelectOptions = this.userService.managerUsers.map(m => m.id);
selectedReviewersList: string[] = [];
membersSelectOptions: string[] = [];
@ -58,7 +58,7 @@ export class TeamMembersManagerComponent implements OnInit {
const memberIds = this.selectedMembersList;
const approverIds = this.selectedApproversList;
const dw = {
...this.dossierWrapper,
...this.dossier,
memberIds,
approverIds,
ownerId
@ -120,15 +120,15 @@ export class TeamMembersManagerComponent implements OnInit {
}
private _updateChanged() {
if (this.dossierWrapper.ownerId !== this.selectedOwnerId) {
if (this.dossier.ownerId !== this.selectedOwnerId) {
this.changed = true;
return;
}
const initialMembers = this.dossierWrapper.memberIds.sort();
const initialMembers = [...this.dossier.memberIds].sort();
const currentMembers = this.selectedMembersList.sort();
const initialApprovers = this.dossierWrapper.approverIds.sort();
const initialApprovers = [...this.dossier.approverIds].sort();
const currentApprovers = this.selectedApproversList.sort();
this.changed = this._compareLists(initialMembers, currentMembers) || this._compareLists(initialApprovers, currentApprovers);
@ -147,9 +147,9 @@ export class TeamMembersManagerComponent implements OnInit {
private _loadData() {
this.teamForm = this._formBuilder.group({
owner: [this.dossierWrapper?.ownerId, Validators.required],
approvers: [[...this.dossierWrapper?.approverIds]],
members: [[...this.dossierWrapper?.memberIds]]
owner: [this.dossier?.ownerId, Validators.required],
approvers: [[...this.dossier?.approverIds]],
members: [[...this.dossier?.memberIds]]
});
this.teamForm.get('owner').valueChanges.subscribe(owner => {
if (!this.isApprover(owner)) {

View File

@ -1,6 +1,7 @@
import { Component, ElementRef, EventEmitter, Input, Output, ViewChild } from '@angular/core';
import { CircleButtonTypes } from '@iqser/common-ui';
import { UserService } from '@services/user.service';
import { List } from '@redaction/red-ui-http';
@Component({
selector: 'redaction-team-members',
@ -11,7 +12,7 @@ export class TeamMembersComponent {
readonly circleButtonTypes = CircleButtonTypes;
readonly currentUser = this._userService.currentUser;
@Input() memberIds: string[];
@Input() memberIds: List;
@Input() perLine: number;
@Input() canAdd = true;
@Input() largeSpacing = false;
@ -30,7 +31,7 @@ export class TeamMembersComponent {
return this.perLine - (this.canAdd ? 1 : 0);
}
get displayedMembers(): string[] {
get displayedMembers(): List {
return this.expandedTeam || !this.overflowCount ? this.memberIds : this.memberIds.slice(0, this.maxTeamMembersBeforeExpand - 1);
}

View File

@ -1,57 +1,57 @@
<ng-container *ngIf="!filter.icon">
<redaction-annotation-icon
*ngIf="filter.key === 'redaction'"
*ngIf="filter.id === 'redaction'"
[color]="dictionaryColor"
label="R"
type="square"
></redaction-annotation-icon>
<redaction-annotation-icon
*ngIf="filter.key === 'recommendation'"
*ngIf="filter.id === 'recommendation'"
[color]="dictionaryColor"
label="R"
type="hexagon"
></redaction-annotation-icon>
<redaction-annotation-icon *ngIf="filter.key === 'hint'" [color]="dictionaryColor" label="H" type="circle"></redaction-annotation-icon>
<redaction-annotation-icon *ngIf="filter.id === 'hint'" [color]="dictionaryColor" label="H" type="circle"></redaction-annotation-icon>
<redaction-annotation-icon
*ngIf="filter.key === 'manual-redaction'"
*ngIf="filter.id === 'manual-redaction'"
[color]="dictionaryColor"
label="M"
type="square"
></redaction-annotation-icon>
<redaction-annotation-icon
*ngIf="filter.key === 'skipped'"
*ngIf="filter.id === 'skipped'"
[color]="dictionaryColor"
label="S"
type="square"
></redaction-annotation-icon>
<redaction-annotation-icon
*ngIf="isSuggestion(filter.key)"
*ngIf="isSuggestion(filter.id)"
[color]="dictionaryColor"
label="S"
type="rhombus"
></redaction-annotation-icon>
<redaction-annotation-icon
*ngIf="needsAnalysis(filter.key)"
*ngIf="needsAnalysis(filter.id)"
[color]="dictionaryColor"
label="A"
type="square"
></redaction-annotation-icon>
<redaction-annotation-icon
*ngIf="filter.key === 'declined-suggestion'"
*ngIf="filter.id === 'declined-suggestion'"
[color]="dictionaryColor"
label="S"
type="rhombus"
></redaction-annotation-icon>
<redaction-annotation-icon *ngIf="filter.key === 'none'" color="transparent" label="-" type="none"></redaction-annotation-icon>
<redaction-annotation-icon *ngIf="filter.id === 'none'" color="transparent" label="-" type="none"></redaction-annotation-icon>
<redaction-annotation-icon
*ngIf="filter.key === 'updated'"
*ngIf="filter.id === 'updated'"
[color]="dictionaryColor"
label="U"
type="square"
></redaction-annotation-icon>
<redaction-annotation-icon *ngIf="filter.key === 'image'" [color]="dictionaryColor" label="I" type="square"></redaction-annotation-icon>
<redaction-annotation-icon *ngIf="filter.id === 'image'" [color]="dictionaryColor" label="I" type="square"></redaction-annotation-icon>
<div *ngIf="filter.key === 'comment'">
<div *ngIf="filter.id === 'comment'">
<mat-icon svgIcon="red:comment"></mat-icon>
</div>
</ng-container>

View File

@ -1,6 +1,6 @@
import { Component, Input, OnInit } from '@angular/core';
import { AppStateService } from '@state/app-state.service';
import { NestedFilter } from '@iqser/common-ui';
import { INestedFilter } from '@iqser/common-ui';
@Component({
selector: 'redaction-type-filter',
@ -8,7 +8,7 @@ import { NestedFilter } from '@iqser/common-ui';
styleUrls: ['./type-filter.component.scss']
})
export class TypeFilterComponent implements OnInit {
@Input() filter: NestedFilter;
@Input() filter: INestedFilter;
dictionaryColor: string;
@ -36,6 +36,6 @@ export class TypeFilterComponent implements OnInit {
needsAnalysis = (key: string) => this._needsAnalysisKeys.includes(key);
ngOnInit(): void {
this.dictionaryColor = this._appStateService.getDictionaryColor(this.filter.key);
this.dictionaryColor = this._appStateService.getDictionaryColor(this.filter.id);
}
}

View File

@ -1,6 +1,6 @@
import { Component } from '@angular/core';
import { MatDialogRef } from '@angular/material/dialog';
import { Dossier, DossierTemplateModel, ReportTemplate, ReportTemplateControllerService } from '@redaction/red-ui-http';
import { DownloadFileType, IDossier, IDossierTemplate, ReportTemplate, ReportTemplateControllerService } from '@redaction/red-ui-http';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { AppStateService } from '@state/app-state.service';
import * as moment from 'moment';
@ -16,12 +16,11 @@ export class AddDossierDialogComponent {
dossierForm: FormGroup;
hasDueDate = false;
downloadTypesEnum: Dossier.DownloadFileTypesEnum[] = ['ORIGINAL', 'PREVIEW', 'REDACTED'];
downloadTypes: { key: Dossier.DownloadFileTypesEnum; label: string }[] = this.downloadTypesEnum.map(type => ({
downloadTypes: { key: DownloadFileType; label: string }[] = ['ORIGINAL', 'PREVIEW', 'REDACTED'].map((type: DownloadFileType) => ({
key: type,
label: downloadTypesTranslations[type]
}));
dossierTemplates: DossierTemplateModel[];
dossierTemplates: IDossierTemplate[];
availableReportTypes = [];
reportTemplateValueMapper = (reportTemplate: ReportTemplate) => reportTemplate.templateId;
@ -29,7 +28,7 @@ export class AddDossierDialogComponent {
private readonly _appStateService: AppStateService,
private readonly _formBuilder: FormBuilder,
private readonly _reportTemplateController: ReportTemplateControllerService,
public dialogRef: MatDialogRef<AddDossierDialogComponent>
readonly dialogRef: MatDialogRef<AddDossierDialogComponent>
) {
this._filterInvalidDossierTemplates();
this.dossierForm = this._formBuilder.group(
@ -68,21 +67,14 @@ export class AddDossierDialogComponent {
}
async saveDossier() {
const dossier: Dossier = this._formToObject();
const foundDossier = this._appStateService.allDossiers.find(p => p.dossierId === dossier.dossierId);
if (foundDossier) {
dossier.memberIds = foundDossier.memberIds;
}
const savedDossier = await this._appStateService.createOrUpdateDossier(dossier);
const savedDossier = await this._appStateService.createOrUpdateDossier(this._formToObject());
if (savedDossier) {
this.dialogRef.close({ dossier: savedDossier });
}
}
async saveDossierAndAddMembers() {
const dossier: Dossier = this._formToObject();
const dossier: IDossier = this._formToObject();
const savedDossier = await this._appStateService.createOrUpdateDossier(dossier);
if (savedDossier) {
this.dialogRef.close({ addMembers: true, dossier: savedDossier });
@ -124,7 +116,7 @@ export class AddDossierDialogComponent {
});
}
private _formToObject(): Dossier {
private _formToObject(): IDossier {
return {
dossierName: this.dossierForm.get('dossierName').value,
description: this.dossierForm.get('description').value,

View File

@ -1,18 +1,18 @@
import { Component, Inject } from '@angular/core';
import { StatusControllerService } from '@redaction/red-ui-http';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { AppStateService } from '@state/app-state.service';
import { UserService } from '@services/user.service';
import { Toaster } from '@iqser/common-ui';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { FileStatusWrapper } from '@models/file/file-status.wrapper';
import { DossierWrapper } from '@state/model/dossier.wrapper';
import { File } from '@models/file/file';
import { Dossier } from '@state/model/dossier';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { FilesService } from '../../services/files.service';
class DialogData {
mode: 'approver' | 'reviewer';
dossier?: DossierWrapper;
files?: FileStatusWrapper[];
dossier?: Dossier;
files?: File[];
ignoreChanged?: boolean;
}
@ -28,8 +28,8 @@ export class AssignReviewerApproverDialogComponent {
readonly userService: UserService,
private readonly _toaster: Toaster,
private readonly _formBuilder: FormBuilder,
private readonly _statusControllerService: StatusControllerService,
private readonly _appStateService: AppStateService,
private readonly _filesService: FilesService,
private readonly _dialogRef: MatDialogRef<AssignReviewerApproverDialogComponent>,
@Inject(MAT_DIALOG_DATA) readonly data: DialogData
) {
@ -69,16 +69,17 @@ export class AssignReviewerApproverDialogComponent {
const selectedUser = this.selectedSingleUser;
if (this.data.mode === 'reviewer') {
await this._statusControllerService
.setFileReviewerForList(
console.log('assign reviewer');
await this._filesService
.setReviewerFor(
this.data.files.map(f => f.fileId),
this._appStateService.activeDossierId,
selectedUser
)
.toPromise();
} else {
await this._statusControllerService
.setStatusUnderApprovalForList(
await this._filesService
.setUnderApprovalFor(
this.data.files.map(f => f.fileId),
selectedUser,
this._appStateService.activeDossierId

View File

@ -1,9 +1,9 @@
import { Component, Inject, OnInit } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';
import { FileAttributeConfig, FileAttributesControllerService, FileStatus } from '@redaction/red-ui-http';
import { FileAttributesControllerService, IFile, IFileAttributeConfig } from '@redaction/red-ui-http';
import { AppStateService } from '@state/app-state.service';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { DossierWrapper } from '@state/model/dossier.wrapper';
import { Dossier } from '@state/model/dossier';
@Component({
templateUrl: './document-info-dialog.component.html',
@ -11,17 +11,17 @@ import { DossierWrapper } from '@state/model/dossier.wrapper';
})
export class DocumentInfoDialogComponent implements OnInit {
documentInfoForm: FormGroup;
file: FileStatus;
attributes: FileAttributeConfig[];
file: IFile;
attributes: IFileAttributeConfig[];
private _dossier: DossierWrapper;
private _dossier: Dossier;
constructor(
private readonly _appStateService: AppStateService,
private readonly _formBuilder: FormBuilder,
private readonly _fileAttributesService: FileAttributesControllerService,
public dialogRef: MatDialogRef<DocumentInfoDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: FileStatus
@Inject(MAT_DIALOG_DATA) public data: IFile
) {
this.file = this.data;
this._dossier = this._appStateService.getDossierById(this.file.dossierId);

View File

@ -1,7 +1,6 @@
import { Component, ElementRef, EventEmitter, Input, OnInit, Output, QueryList, ViewChildren } from '@angular/core';
import { EditDossierSectionInterface } from '../edit-dossier-section.interface';
import { DossierWrapper } from '@state/model/dossier.wrapper';
import { AppStateService } from '@state/app-state.service';
import { Dossier } from '../../../../../state/model/dossier';
import { PermissionsService } from '@services/permissions.service';
import { CircleButtonTypes, IconButtonTypes, LoadingService } from '@iqser/common-ui';
import { FormBuilder, FormGroup } from '@angular/forms';
@ -18,8 +17,8 @@ export class EditDossierAttributesComponent implements EditDossierSectionInterfa
readonly iconButtonTypes = IconButtonTypes;
readonly circleButtonTypes = CircleButtonTypes;
@Input() dossierWrapper: DossierWrapper;
@Output() updateDossier = new EventEmitter<any>();
@Input() dossier: Dossier;
@Output() readonly updateDossier = new EventEmitter();
customAttributes: DossierAttributeWithValue[] = [];
imageAttributes: DossierAttributeWithValue[] = [];
attributes: DossierAttributeWithValue[] = [];
@ -28,7 +27,6 @@ export class EditDossierAttributesComponent implements EditDossierSectionInterfa
@ViewChildren('fileInput') private _fileInputs: QueryList<ElementRef>;
constructor(
private readonly _appStateService: AppStateService,
private readonly _permissionsService: PermissionsService,
private readonly _dossierAttributesService: DossierAttributesService,
private readonly _loadingService: LoadingService,
@ -50,11 +48,11 @@ export class EditDossierAttributesComponent implements EditDossierSectionInterfa
}
get disabled() {
return !this._permissionsService.isOwner(this.dossierWrapper);
return !this._permissionsService.isOwner(this.dossier);
}
get canEdit(): boolean {
return this._permissionsService.isOwner(this.dossierWrapper);
return this._permissionsService.isOwner(this.dossier);
}
async ngOnInit() {
@ -70,7 +68,7 @@ export class EditDossierAttributesComponent implements EditDossierSectionInterfa
dossierAttributeId: attr.id,
value: this.currentAttrValue(attr)
}));
await this._dossierAttributesService.setValues(this.dossierWrapper, dossierAttributeList);
await this._dossierAttributesService.setValues(this.dossier, dossierAttributeList);
await this._loadAttributes();
this.updateDossier.emit();
this._loadingService.stop();
@ -136,7 +134,7 @@ export class EditDossierAttributesComponent implements EditDossierSectionInterfa
}
private async _loadAttributes() {
this.attributes = await this._dossierAttributesService.getValues(this.dossierWrapper);
this.attributes = await this._dossierAttributesService.getValues(this.dossier);
this.customAttributes = this.attributes.filter(attr => !this.isImage(attr));
this.imageAttributes = this.attributes.filter(attr => this.isImage(attr));
}

View File

@ -1,16 +1,16 @@
import { Component, EventEmitter, forwardRef, Injector, Input, OnInit, Output, TemplateRef, ViewChild } from '@angular/core';
import { EditDossierSectionInterface } from '../edit-dossier-section.interface';
import { DossierWrapper } from '@state/model/dossier.wrapper';
import { Dossier } from '@state/model/dossier';
import {
CircleButtonTypes,
DefaultListingServices,
Listable,
IListable,
ListingComponent,
LoadingService,
SortingOrders,
TableColumnConfig
} from '@iqser/common-ui';
import { FileManagementControllerService, FileStatus, StatusControllerService } from '@redaction/red-ui-http';
import { FileManagementControllerService, IFile } from '@redaction/red-ui-http';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import * as moment from 'moment';
import { ConfigService } from '@services/config.service';
@ -20,8 +20,9 @@ import { distinctUntilChanged, map } from 'rxjs/operators';
import { ConfirmationDialogInput, TitleColors } from '@shared/dialogs/confirmation-dialog/confirmation-dialog.component';
import { DossiersDialogService } from '../../../services/dossiers-dialog.service';
import { AppStateService } from '@state/app-state.service';
import { FilesService } from '../../../services/files.service';
interface FileListItem extends FileStatus, Listable {
interface FileListItem extends IFile, IListable {
readonly canRestore: boolean;
readonly restoreDate: string;
}
@ -36,8 +37,8 @@ interface FileListItem extends FileStatus, Listable {
]
})
export class EditDossierDeletedDocumentsComponent extends ListingComponent<FileListItem> implements EditDossierSectionInterface, OnInit {
@Input() dossierWrapper: DossierWrapper;
@Output() updateDossier = new EventEmitter<any>();
@Input() dossier: Dossier;
@Output() readonly updateDossier = new EventEmitter();
readonly changed = false;
readonly canRestoreSelected$ = this._canRestoreSelected$;
disabled: boolean;
@ -49,13 +50,12 @@ export class EditDossierDeletedDocumentsComponent extends ListingComponent<FileL
@ViewChild('pagesTemplate', { static: true }) pagesTemplate: TemplateRef<never>;
@ViewChild('deletedDateTemplate', { static: true }) deletedDateTemplate: TemplateRef<never>;
@ViewChild('restoreDateTemplate', { static: true }) restoreDateTemplate: TemplateRef<never>;
protected readonly _primaryKey = 'fileId';
constructor(
protected readonly _injector: Injector,
private readonly _statusController: StatusControllerService,
private readonly _fileManagementController: FileManagementControllerService,
private readonly _appStateService: AppStateService,
private readonly _filesService: FilesService,
private readonly _loadingService: LoadingService,
private readonly _configService: ConfigService,
private readonly _dialogService: DossiersDialogService
@ -91,7 +91,7 @@ export class EditDossierDeletedDocumentsComponent extends ListingComponent<FileL
async ngOnInit() {
this._configureTableColumns();
this._loadingService.start();
const files = await this._statusController.getDeletedFileStatus(this.dossierWrapper.dossierId).toPromise();
const files = await this._filesService.getDeletedFilesFor(this.dossier.id).toPromise();
this.entitiesService.setEntities(this._toListItems(files));
this.sortingService.setSortingOption({
column: 'softDeleted',
@ -138,7 +138,7 @@ export class EditDossierDeletedDocumentsComponent extends ListingComponent<FileL
private async _restore(files: FileListItem[]): Promise<void> {
const fileIds = files.map(f => f.fileId);
await this._fileManagementController.restoreFiles(fileIds, this.dossierWrapper.dossierId).toPromise();
await this._fileManagementController.restoreFiles(fileIds, this.dossier.id).toPromise();
this._removeFromList(fileIds);
await this._appStateService.reloadActiveDossierFiles();
this.updateDossier.emit();
@ -146,7 +146,7 @@ export class EditDossierDeletedDocumentsComponent extends ListingComponent<FileL
private async _hardDelete(files: FileListItem[]) {
const fileIds = files.map(f => f.fileId);
await this._fileManagementController.hardDeleteFile(this.dossierWrapper.dossierId, fileIds).toPromise();
await this._fileManagementController.hardDeleteFile(this.dossier.id, fileIds).toPromise();
this._removeFromList(fileIds);
this.updateDossier.emit();
}
@ -157,16 +157,17 @@ export class EditDossierDeletedDocumentsComponent extends ListingComponent<FileL
this.entitiesService.setSelected([]);
}
private _toListItems(files: FileStatus[]): FileListItem[] {
private _toListItems(files: IFile[]): FileListItem[] {
return files.map(file => this._toListItem(file));
}
private _toListItem(file: FileStatus): FileListItem {
private _toListItem(file: IFile): FileListItem {
const restoreDate = this._getRestoreDate(file.softDeleted);
return {
id: file.fileId,
...file,
restoreDate,
searchKey: file.filename,
canRestore: this._canRestoreFile(restoreDate)
};
}

View File

@ -1,10 +1,10 @@
<div class="header-wrapper">
<div class="heading">
<div>{{ dossierWrapper.type?.label }}</div>
<div>{{ dossier.type?.label }}</div>
<div class="small-label stats-subtitle">
<div>
<mat-icon svgIcon="red:entries"></mat-icon>
{{ 'edit-dossier-dialog.dictionary.entries' | translate: { length: (dossierWrapper.type?.entries || []).length } }}
{{ 'edit-dossier-dialog.dictionary.entries' | translate: { length: (dossier.type?.entries || []).length } }}
</div>
</div>
</div>
@ -19,13 +19,13 @@
[placeholder]="'edit-dossier-dialog.dictionary.display-name.placeholder' | translate"
[saveTooltip]="'edit-dossier-dialog.dictionary.display-name.save' | translate"
[showPreview]="false"
[value]="dossierWrapper.type?.label"
[value]="dossier.type?.label"
></iqser-editable-input>
</div>
</div>
<redaction-dictionary-manager
[canEdit]="canEdit"
[initialEntries]="dossierWrapper.type?.entries || []"
[initialEntries]="dossier.type?.entries || []"
[withFloatingActions]="false"
></redaction-dictionary-manager>

View File

@ -1,13 +1,13 @@
import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core';
import { AppStateService } from '@state/app-state.service';
import { DossierWrapper } from '@state/model/dossier.wrapper';
import { Dossier } from '@state/model/dossier';
import { EditDossierSectionInterface } from '../edit-dossier-section.interface';
import { PermissionsService } from '@services/permissions.service';
import { DictionaryManagerComponent } from '@shared/components/dictionary-manager/dictionary-manager.component';
import { DictionarySaveService } from '@shared/services/dictionary-save.service';
import { DictionaryService } from '@shared/services/dictionary.service';
import { FormBuilder } from '@angular/forms';
import { CircleButtonTypes, LoadingService } from '@iqser/common-ui';
import { Dictionary, DictionaryControllerService } from '@redaction/red-ui-http';
import { IDictionary } from '@redaction/red-ui-http';
@Component({
selector: 'redaction-edit-dossier-dictionary',
@ -15,8 +15,8 @@ import { Dictionary, DictionaryControllerService } from '@redaction/red-ui-http'
styleUrls: ['./edit-dossier-dictionary.component.scss']
})
export class EditDossierDictionaryComponent implements EditDossierSectionInterface, OnInit {
@Input() dossierWrapper: DossierWrapper;
@Output() updateDossier: EventEmitter<any> = new EventEmitter<any>();
@Input() dossier: Dossier;
@Output() readonly updateDossier = new EventEmitter();
canEdit = false;
readonly circleButtonTypes = CircleButtonTypes;
@ -24,13 +24,12 @@ export class EditDossierDictionaryComponent implements EditDossierSectionInterfa
constructor(
private readonly _appStateService: AppStateService,
private readonly _dictionarySaveService: DictionarySaveService,
private readonly _dictionaryService: DictionaryService,
private readonly _permissionsService: PermissionsService,
private readonly _dictionaryControllerService: DictionaryControllerService,
private readonly _loadingService: LoadingService,
private readonly _formBuilder: FormBuilder
) {
this.canEdit = this._permissionsService.isDossierMember(this.dossierWrapper);
this.canEdit = this._permissionsService.isDossierMember(this.dossier);
}
get changed() {
@ -43,31 +42,31 @@ export class EditDossierDictionaryComponent implements EditDossierSectionInterfa
async ngOnInit() {
this._loadingService.start();
await this._appStateService.updateDossierDictionary(this.dossierWrapper.dossierTemplateId, this.dossierWrapper.dossierId);
await this._appStateService.updateDossierDictionary(this.dossier.dossierTemplateId, this.dossier.id);
this._loadingService.stop();
}
async updateDisplayName(label: string) {
const typeValue: Dictionary = { ...this.dossierWrapper.type, label };
await this._dictionaryControllerService
.updateType(typeValue, this.dossierWrapper.dossierTemplateId, 'dossier_redaction', this.dossierWrapper.dossierId)
const typeValue: IDictionary = { ...this.dossier.type, label };
await this._dictionaryService
.updateType(typeValue, this.dossier.dossierTemplateId, 'dossier_redaction', this.dossier.id)
.toPromise();
await this._appStateService.updateDossierDictionary(this.dossierWrapper.dossierTemplateId, this.dossierWrapper.dossierId);
await this._appStateService.updateDossierDictionary(this.dossier.dossierTemplateId, this.dossier.id);
this.updateDossier.emit();
}
async save() {
await this._dictionarySaveService
await this._dictionaryService
.saveEntries(
this._dictionaryManager.currentEntries,
this._dictionaryManager.initialEntries,
this.dossierWrapper.dossierTemplateId,
this.dossier.dossierTemplateId,
'dossier_redaction',
this.dossierWrapper.dossierId,
this.dossier.id,
false
)
.toPromise();
await this._appStateService.updateDossierDictionary(this.dossierWrapper.dossierTemplateId, this.dossierWrapper.dossierId);
await this._appStateService.updateDossierDictionary(this.dossier.dossierTemplateId, this.dossier.id);
this.updateDossier.emit();
}

View File

@ -1,8 +1,8 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { Dossier, DossierTemplateModel, ReportTemplate, ReportTemplateControllerService } from '@redaction/red-ui-http';
import { DownloadFileType, ReportTemplate, ReportTemplateControllerService } from '@redaction/red-ui-http';
import { FormBuilder, FormGroup } from '@angular/forms';
import { AppStateService } from '@state/app-state.service';
import { DossierWrapper } from '@state/model/dossier.wrapper';
import { Dossier } from '@state/model/dossier';
import { EditDossierSectionInterface } from '../edit-dossier-section.interface';
import { downloadTypesTranslations } from '../../../../../translations/download-types-translations';
@ -13,16 +13,14 @@ import { downloadTypesTranslations } from '../../../../../translations/download-
})
export class EditDossierDownloadPackageComponent implements OnInit, EditDossierSectionInterface {
dossierForm: FormGroup;
downloadTypesEnum: Dossier.DownloadFileTypesEnum[] = ['ORIGINAL', 'PREVIEW', 'REDACTED'];
downloadTypes: { key: Dossier.DownloadFileTypesEnum; label: string }[] = this.downloadTypesEnum.map(type => ({
downloadTypes: { key: DownloadFileType; label: string }[] = ['ORIGINAL', 'PREVIEW', 'REDACTED'].map((type: DownloadFileType) => ({
key: type,
label: downloadTypesTranslations[type]
}));
dossierTemplates: DossierTemplateModel[];
availableReportTypes: ReportTemplate[] = [];
@Input() dossierWrapper: DossierWrapper;
@Output() updateDossier = new EventEmitter<any>();
@Input() dossier: Dossier;
@Output() readonly updateDossier = new EventEmitter();
constructor(
private readonly _appStateService: AppStateService,
@ -41,11 +39,11 @@ export class EditDossierDownloadPackageComponent implements OnInit, EditDossierS
get changed() {
if (this.dossierForm) {
for (const key of Object.keys(this.dossierForm.getRawValue())) {
if (this.dossierWrapper[key].length !== this.dossierForm.get(key).value.length) {
if (this.dossier[key].length !== this.dossierForm.get(key).value.length) {
return true;
}
const originalItems = [...this.dossierWrapper[key]].sort();
const originalItems = [...this.dossier[key]].sort();
const newItems = [...this.dossierForm.get(key).value].sort();
for (let idx = 0; idx < originalItems.length; ++idx) {
@ -67,12 +65,12 @@ export class EditDossierDownloadPackageComponent implements OnInit, EditDossierS
async ngOnInit() {
this.availableReportTypes =
(await this._reportTemplateController.getAvailableReportTemplates(this.dossierWrapper.dossierTemplateId).toPromise()) || [];
(await this._reportTemplateController.getAvailableReportTemplates(this.dossier.dossierTemplateId).toPromise()) || [];
this.dossierForm = this._formBuilder.group(
{
reportTemplateIds: [this.dossierWrapper.reportTemplateIds],
downloadFileTypes: [this.dossierWrapper.downloadFileTypes]
reportTemplateIds: [this.dossier.reportTemplateIds],
downloadFileTypes: [this.dossier.downloadFileTypes]
},
{
validators: control =>
@ -85,7 +83,7 @@ export class EditDossierDownloadPackageComponent implements OnInit, EditDossierS
async save() {
const dossier = {
...this.dossierWrapper,
...this.dossier,
downloadFileTypes: this.dossierForm.get('downloadFileTypes').value,
reportTemplateIds: this.dossierForm.get('reportTemplateIds').value
};
@ -95,8 +93,8 @@ export class EditDossierDownloadPackageComponent implements OnInit, EditDossierS
revert() {
this.dossierForm.reset({
downloadFileTypes: this.dossierWrapper.downloadFileTypes,
reportTemplateIds: this.dossierWrapper.reportTemplateIds
downloadFileTypes: this.dossier.downloadFileTypes,
reportTemplateIds: this.dossier.reportTemplateIds
});
}
}

View File

@ -1,6 +1,6 @@
<section class="dialog">
<div class="dialog-header heading-l">
{{ 'edit-dossier-dialog.header' | translate: { dossierName: dossierWrapper.dossierName } }}
{{ 'edit-dossier-dialog.header' | translate: { dossierName: dossier.dossierName } }}
</div>
<div class="dialog-content">
@ -22,37 +22,37 @@
<redaction-edit-dossier-general-info
(updateDossier)="updatedDossier()"
*ngIf="activeNav === 'dossierInfo'"
[dossierWrapper]="dossierWrapper"
[dossier]="dossier"
></redaction-edit-dossier-general-info>
<redaction-edit-dossier-download-package
(updateDossier)="updatedDossier()"
*ngIf="activeNav === 'downloadPackage'"
[dossierWrapper]="dossierWrapper"
[dossier]="dossier"
></redaction-edit-dossier-download-package>
<redaction-edit-dossier-dictionary
(updateDossier)="updatedDossier()"
*ngIf="activeNav === 'dossierDictionary'"
[dossierWrapper]="dossierWrapper"
[dossier]="dossier"
></redaction-edit-dossier-dictionary>
<redaction-edit-dossier-team-members
(updateDossier)="updatedDossier()"
*ngIf="activeNav === 'members'"
[dossierWrapper]="dossierWrapper"
[dossier]="dossier"
></redaction-edit-dossier-team-members>
<redaction-edit-dossier-attributes
(updateDossier)="updatedDossier()"
*ngIf="activeNav === 'dossierAttributes'"
[dossierWrapper]="dossierWrapper"
[dossier]="dossier"
></redaction-edit-dossier-attributes>
<redaction-edit-dossier-deleted-documents
(updateDossier)="updatedDossier()"
*ngIf="activeNav === 'deletedDocuments'"
[dossierWrapper]="dossierWrapper"
[dossier]="dossier"
></redaction-edit-dossier-deleted-documents>
</div>

View File

@ -1,6 +1,6 @@
import { ChangeDetectorRef, Component, Inject, ViewChild } from '@angular/core';
import { MAT_DIALOG_DATA } from '@angular/material/dialog';
import { DossierWrapper } from '@state/model/dossier.wrapper';
import { Dossier } from '@state/model/dossier';
import { EditDossierGeneralInfoComponent } from './general-info/edit-dossier-general-info.component';
import { EditDossierDownloadPackageComponent } from './download-package/edit-dossier-download-package.component';
import { EditDossierSectionInterface } from './edit-dossier-section.interface';
@ -22,7 +22,7 @@ type Section = 'dossierInfo' | 'downloadPackage' | 'dossierDictionary' | 'member
export class EditDossierDialogComponent {
readonly navItems: { key: Section; title?: string; sideNavTitle?: string }[];
activeNav: Section;
dossierWrapper: DossierWrapper;
dossier: Dossier;
@ViewChild(EditDossierGeneralInfoComponent) generalInfoComponent: EditDossierGeneralInfoComponent;
@ViewChild(EditDossierDownloadPackageComponent) downloadPackageComponent: EditDossierDownloadPackageComponent;
@ -37,7 +37,7 @@ export class EditDossierDialogComponent {
private readonly _changeRef: ChangeDetectorRef,
@Inject(MAT_DIALOG_DATA)
private readonly _data: {
dossierWrapper: DossierWrapper;
dossier: Dossier;
afterSave: Function;
section?: Section;
}
@ -73,7 +73,7 @@ export class EditDossierDialogComponent {
}
];
this.dossierWrapper = _data.dossierWrapper;
this.dossier = _data.dossier;
this.activeNav = _data.section || 'dossierInfo';
}
@ -105,8 +105,8 @@ export class EditDossierDialogComponent {
}
updatedDossier() {
this._toaster.success(_('edit-dossier-dialog.change-successful'), { params: { dossierName: this.dossierWrapper.dossierName } });
this.dossierWrapper = this._appStateService.getDossierById(this.dossierWrapper.dossierId);
this._toaster.success(_('edit-dossier-dialog.change-successful'), { params: { dossierName: this.dossier.dossierName } });
this.dossier = this._appStateService.getDossierById(this.dossier.id);
this._changeRef.detectChanges();
this.afterSave();
}

View File

@ -58,7 +58,7 @@
<div class="dialog-actions">
<iqser-icon-button
(action)="deleteDossier()"
*ngIf="permissionsService.canDeleteDossier(dossierWrapper)"
*ngIf="permissionsService.canDeleteDossier(dossier)"
[label]="'dossier-listing.delete.action' | translate"
[type]="iconButtonTypes.dark"
icon="red:trash"

View File

@ -1,19 +1,18 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { DossierTemplateModel } from '@redaction/red-ui-http';
import { IDossierTemplate } from '@redaction/red-ui-http';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { AppStateService } from '../../../../../state/app-state.service';
import { AppStateService } from '@state/app-state.service';
import * as moment from 'moment';
import { DossierWrapper } from '@state/model/dossier.wrapper';
import { Dossier } from '@state/model/dossier';
import { EditDossierSectionInterface } from '../edit-dossier-section.interface';
import { DossiersDialogService } from '../../../services/dossiers-dialog.service';
import { PermissionsService } from '@services/permissions.service';
import { Router } from '@angular/router';
import { MatDialogRef } from '@angular/material/dialog';
import { EditDossierDialogComponent } from '../edit-dossier-dialog.component';
import { Toaster } from '@iqser/common-ui';
import { IconButtonTypes, Toaster } from '@iqser/common-ui';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { IconButtonTypes } from '@iqser/common-ui';
import { ConfirmationDialogInput, TitleColors } from '../../../../shared/dialogs/confirmation-dialog/confirmation-dialog.component';
import { ConfirmationDialogInput, TitleColors } from '@shared/dialogs/confirmation-dialog/confirmation-dialog.component';
@Component({
selector: 'redaction-edit-dossier-general-info',
@ -25,10 +24,10 @@ export class EditDossierGeneralInfoComponent implements OnInit, EditDossierSecti
dossierForm: FormGroup;
hasDueDate: boolean;
dossierTemplates: DossierTemplateModel[];
dossierTemplates: IDossierTemplate[];
@Input() dossierWrapper: DossierWrapper;
@Output() updateDossier = new EventEmitter<any>();
@Input() dossier: Dossier;
@Output() readonly updateDossier = new EventEmitter();
constructor(
readonly permissionsService: PermissionsService,
@ -43,13 +42,13 @@ export class EditDossierGeneralInfoComponent implements OnInit, EditDossierSecti
get changed() {
for (const key of Object.keys(this.dossierForm.getRawValue())) {
if (key === 'dueDate') {
if (this.hasDueDate !== !!this.dossierWrapper.dueDate) {
if (this.hasDueDate !== !!this.dossier.dueDate) {
return true;
}
if (this.hasDueDate && !moment(this.dossierWrapper.dueDate).isSame(moment(this.dossierForm.get(key).value))) {
if (this.hasDueDate && !moment(this.dossier.dueDate).isSame(moment(this.dossierForm.get(key).value))) {
return true;
}
} else if (this.dossierWrapper[key] !== this.dossierForm.get(key).value) {
} else if (this.dossier[key] !== this.dossierForm.get(key).value) {
return true;
}
}
@ -68,34 +67,34 @@ export class EditDossierGeneralInfoComponent implements OnInit, EditDossierSecti
ngOnInit() {
this._filterInvalidDossierTemplates();
this.dossierForm = this._formBuilder.group({
dossierName: [this.dossierWrapper.dossierName, Validators.required],
dossierName: [this.dossier.dossierName, Validators.required],
dossierTemplateId: [
{
value: this.dossierWrapper.dossierTemplateId,
disabled: this.dossierWrapper.hasFiles
value: this.dossier.dossierTemplateId,
disabled: this.dossier.hasFiles
},
Validators.required
],
description: [this.dossierWrapper.description],
dueDate: [this.dossierWrapper.dueDate],
watermarkEnabled: [this.dossierWrapper.watermarkEnabled]
description: [this.dossier.description],
dueDate: [this.dossier.dueDate],
watermarkEnabled: [this.dossier.watermarkEnabled]
});
this.hasDueDate = !!this.dossierWrapper.dueDate;
this.hasDueDate = !!this.dossier.dueDate;
}
revert() {
this.dossierForm.reset({
dossierName: this.dossierWrapper.dossierName,
dossierTemplateId: this.dossierWrapper.dossierTemplateId,
description: this.dossierWrapper.description,
watermarkEnabled: this.dossierWrapper.watermarkEnabled,
dueDate: this.dossierWrapper.dueDate
dossierName: this.dossier.dossierName,
dossierTemplateId: this.dossier.dossierTemplateId,
description: this.dossier.description,
watermarkEnabled: this.dossier.watermarkEnabled,
dueDate: this.dossier.dueDate
});
}
async save() {
const dossier = {
...this.dossierWrapper,
...this.dossier,
dossierName: this.dossierForm.get('dossierName').value,
description: this.dossierForm.get('description').value,
watermarkEnabled: this.dossierForm.get('watermarkEnabled').value,
@ -116,12 +115,12 @@ export class EditDossierGeneralInfoComponent implements OnInit, EditDossierSecti
requireInput: true,
denyText: _('confirmation-dialog.delete-dossier.deny-text'),
translateParams: {
dossierName: this.dossierWrapper.dossierName,
dossierName: this.dossier.dossierName,
dossiersCount: 1
}
});
this._dialogService.openDialog('confirm', null, data, async () => {
await this._appStateService.deleteDossier(this.dossierWrapper);
await this._appStateService.deleteDossier(this.dossier);
this._editDossierDialogRef.componentInstance.afterSave();
this._editDossierDialogRef.close();
this._router.navigate(['main', 'dossiers']).then(() => this._notifyDossierDeleted());
@ -129,12 +128,12 @@ export class EditDossierGeneralInfoComponent implements OnInit, EditDossierSecti
}
private _notifyDossierDeleted() {
this._toaster.success(_('edit-dossier-dialog.delete-successful'), { params: { dossierName: this.dossierWrapper.dossierName } });
this._toaster.success(_('edit-dossier-dialog.delete-successful'), { params: { dossierName: this.dossier.dossierName } });
}
private _filterInvalidDossierTemplates() {
this.dossierTemplates = this._appStateService.dossierTemplates.filter(r => {
if (this.dossierWrapper?.dossierTemplateId === r.dossierTemplateId) {
if (this.dossier?.dossierTemplateId === r.dossierTemplateId) {
return true;
}
const notYetValid = !!r.validFrom && moment(r.validFrom).isAfter(moment());

View File

@ -1 +1 @@
<redaction-team-members-manager (save)="updateDossier.emit()" [dossierWrapper]="dossierWrapper"></redaction-team-members-manager>
<redaction-team-members-manager (save)="updateDossier.emit()" [dossier]="dossier"></redaction-team-members-manager>

View File

@ -1,6 +1,5 @@
import { Component, EventEmitter, Input, Output, ViewChild } from '@angular/core';
import { AppStateService } from '@state/app-state.service';
import { DossierWrapper } from '@state/model/dossier.wrapper';
import { Dossier } from '@state/model/dossier';
import { EditDossierSectionInterface } from '../edit-dossier-section.interface';
import { TeamMembersManagerComponent } from '../../../components/team-members-manager/team-members-manager.component';
import { UserService } from '@services/user.service';
@ -13,12 +12,12 @@ import { UserService } from '@services/user.service';
export class EditDossierTeamMembersComponent implements EditDossierSectionInterface {
readonly currentUser = this._userService.currentUser;
@Input() dossierWrapper: DossierWrapper;
@Output() updateDossier = new EventEmitter<any>();
@Input() dossier: Dossier;
@Output() readonly updateDossier = new EventEmitter();
@ViewChild(TeamMembersManagerComponent) managerComponent: TeamMembersManagerComponent;
constructor(private readonly _appStateService: AppStateService, private readonly _userService: UserService) {}
constructor(private readonly _userService: UserService) {}
get changed() {
return this.managerComponent.changed;

View File

@ -10,7 +10,7 @@ import { ManualRedactionEntryWrapper } from '@models/file/manual-redaction-entry
import { ManualAnnotationService } from '../../services/manual-annotation.service';
import { ManualAnnotationResponse } from '@models/file/manual-annotation-response';
import { PermissionsService } from '@services/permissions.service';
import { TypeValueWrapper } from '@models/file/type-value.wrapper';
import { TypeValue } from '@models/file/type-value';
export interface LegalBasisOption {
label?: string;
@ -30,7 +30,7 @@ export class ManualAnnotationDialogComponent implements OnInit {
isDictionaryRequest: boolean;
isFalsePositiveRequest: boolean;
redactionDictionaries: TypeValueWrapper[] = [];
redactionDictionaries: TypeValue[] = [];
legalOptions: LegalBasisOption[] = [];
constructor(

View File

@ -33,13 +33,13 @@
<ng-template #nameTemplate let-dossier="entity">
<div class="cell">
<div [matTooltip]="dossier.dossierName" class="table-item-title heading" matTooltipPosition="above">
<div [matTooltip]="dossier.dossierName" class="table-item-title heading mb-6" matTooltipPosition="above">
{{ dossier.dossierName }}
</div>
<div class="small-label stats-subtitle">
<div class="small-label stats-subtitle mb-6">
<div>
<mat-icon svgIcon="red:template"></mat-icon>
{{ dossier.dossierTemplateName }}
{{ getDossierTemplateNameFor(dossier.dossierTemplateId) }}
</div>
</div>
<div class="small-label stats-subtitle">
@ -53,7 +53,7 @@
</div>
<div>
<mat-icon svgIcon="red:user"></mat-icon>
{{ dossier.memberCount }}
{{ dossier.memberIds.length }}
</div>
<div>
<mat-icon svgIcon="red:calendar"></mat-icon>

View File

@ -1,11 +1,11 @@
import { AfterViewInit, Component, forwardRef, Injector, OnDestroy, OnInit, TemplateRef, ViewChild } from '@angular/core';
import { Dossier } from '@redaction/red-ui-http';
import { DossierStatuses } from '@redaction/red-ui-http';
import { AppStateService } from '@state/app-state.service';
import { UserService } from '@services/user.service';
import { DoughnutChartConfig } from '@shared/components/simple-doughnut-chart/simple-doughnut-chart.component';
import { groupBy } from '@utils/functions';
import { TranslateService } from '@ngx-translate/core';
import { DossierWrapper } from '@state/model/dossier.wrapper';
import { Dossier } from '@state/model/dossier';
import { timer } from 'rxjs';
import { tap } from 'rxjs/operators';
import { TranslateChartService } from '@services/translate-chart.service';
@ -29,7 +29,7 @@ import { PermissionsService } from '@services/permissions.service';
providers: [...DefaultListingServices, { provide: ListingComponent, useExisting: forwardRef(() => DossierListingScreenComponent) }]
})
export class DossierListingScreenComponent
extends ListingComponent<DossierWrapper>
extends ListingComponent<Dossier>
implements OnInit, AfterViewInit, OnDestroy, OnAttach, OnDetach
{
readonly currentUser = this._userService.currentUser;
@ -43,21 +43,20 @@ export class DossierListingScreenComponent
type: 'primary'
}
];
tableColumnConfigs: TableColumnConfig<DossierWrapper>[];
tableColumnConfigs: TableColumnConfig<Dossier>[];
dossiersChartData: DoughnutChartConfig[] = [];
documentsChartData: DoughnutChartConfig[] = [];
@ViewChild('nameTemplate', { static: true }) nameTemplate: TemplateRef<never>;
@ViewChild('needsWorkTemplate', { static: true }) needsWorkTemplate: TemplateRef<never>;
@ViewChild('ownerTemplate', { static: true }) ownerTemplate: TemplateRef<never>;
@ViewChild('statusTemplate', { static: true }) statusTemplate: TemplateRef<never>;
protected readonly _primaryKey = 'dossierName';
@ViewChild('nameTemplate', { static: true }) nameTemplate: TemplateRef<unknown>;
@ViewChild('needsWorkTemplate', { static: true }) needsWorkTemplate: TemplateRef<unknown>;
@ViewChild('ownerTemplate', { static: true }) ownerTemplate: TemplateRef<unknown>;
@ViewChild('statusTemplate', { static: true }) statusTemplate: TemplateRef<unknown>;
private _lastScrolledIndex: number;
@ViewChild('needsWorkFilterTemplate', {
read: TemplateRef,
static: true
})
private readonly _needsWorkFilterTemplate: TemplateRef<unknown>;
@ViewChild(TableComponent) private readonly _tableComponent: TableComponent<DossierWrapper>;
@ViewChild(TableComponent) private readonly _tableComponent: TableComponent<Dossier>;
constructor(
private readonly _router: Router,
@ -76,14 +75,16 @@ export class DossierListingScreenComponent
}
private get _activeDossiersCount(): number {
return this.entitiesService.all.filter(p => p.status === Dossier.StatusEnum.ACTIVE).length;
return this.entitiesService.all.filter(p => p.status === DossierStatuses.ACTIVE).length;
}
private get _inactiveDossiersCount(): number {
return this.entitiesService.all.length - this._activeDossiersCount;
}
routerLinkFn = (dossier: DossierWrapper) => ['/main/dossiers/' + dossier.dossierId];
getDossierTemplateNameFor(dossierTemplateId: string): string {
return this._appStateService.getDossierTemplateById(dossierTemplateId).name;
}
ngOnInit(): void {
this._configureTableColumns();
@ -120,10 +121,10 @@ export class DossierListingScreenComponent
openAddDossierDialog(): void {
this._dialogService.openDialog('addDossier', null, null, async addResponse => {
await this._router.navigate([`/main/dossiers/${addResponse.dossier.dossierId}`]);
await this._router.navigate([`/main/dossiers/${addResponse.dossier.id}`]);
if (addResponse.addMembers) {
this._dialogService.openDialog('editDossier', null, {
dossierWrapper: addResponse.dossier,
dossier: addResponse.dossier,
section: 'members'
});
}
@ -140,12 +141,12 @@ export class DossierListingScreenComponent
const groups = groupBy(this._appStateService.aggregatedFiles, 'status');
this.documentsChartData = [];
for (const key of Object.keys(groups)) {
for (const status of Object.keys(groups)) {
this.documentsChartData.push({
value: groups[key].length,
color: key,
label: fileStatusTranslations[key],
key: key
value: groups[status].length,
color: status,
label: fileStatusTranslations[status],
key: status
});
}
this.documentsChartData.sort(StatusSorter.byStatus);
@ -156,7 +157,7 @@ export class DossierListingScreenComponent
this.tableColumnConfigs = [
{
label: _('dossier-listing.table-col-names.name'),
sortByKey: 'dossierName',
sortByKey: 'searchKey',
template: this.nameTemplate,
width: '2fr'
},
@ -214,23 +215,29 @@ export class DossierListingScreenComponent
allDistinctDossierTemplates.add(entry.dossierTemplateId);
});
const statusFilters = [...allDistinctFileStatus].map<NestedFilter>(status => ({
key: status,
label: this._translateService.instant(fileStatusTranslations[status])
}));
const statusFilters = [...allDistinctFileStatus].map(
status =>
new NestedFilter({
id: status,
label: this._translateService.instant(fileStatusTranslations[status])
})
);
this.filterService.addFilterGroup({
slug: 'statusFilters',
label: this._translateService.instant('filters.status'),
icon: 'red:status',
filters: statusFilters.sort(StatusSorter.byStatus),
filters: statusFilters.sort((a, b) => StatusSorter[a.id] - StatusSorter[b.id]),
checker: dossierStatusChecker
});
const peopleFilters = [...allDistinctPeople].map<NestedFilter>(userId => ({
key: userId,
label: this._userService.getNameForId(userId)
}));
const peopleFilters = [...allDistinctPeople].map(
userId =>
new NestedFilter({
id: userId,
label: this._userService.getNameForId(userId)
})
);
this.filterService.addFilterGroup({
slug: 'peopleFilters',
@ -240,25 +247,31 @@ export class DossierListingScreenComponent
checker: dossierMemberChecker
});
const needsWorkFilters = [...allDistinctNeedsWork].map<NestedFilter>(type => ({
key: type,
label: workloadTranslations[type]
}));
const needsWorkFilters = [...allDistinctNeedsWork].map(
type =>
new NestedFilter({
id: type,
label: workloadTranslations[type]
})
);
this.filterService.addFilterGroup({
slug: 'needsWorkFilters',
label: this._translateService.instant('filters.needs-work'),
icon: 'red:needs-work',
filterTemplate: this._needsWorkFilterTemplate,
filters: needsWorkFilters.sort(RedactionFilterSorter.byKey),
filters: needsWorkFilters.sort((a, b) => RedactionFilterSorter[a.id] - RedactionFilterSorter[b.id]),
checker: annotationFilterChecker,
matchAll: true
});
const dossierTemplateFilters = [...allDistinctDossierTemplates].map<NestedFilter>(id => ({
key: id,
label: this._appStateService.getDossierTemplateById(id).name
}));
const dossierTemplateFilters = [...allDistinctDossierTemplates].map(
id =>
new NestedFilter({
id: id,
label: this._appStateService.getDossierTemplateById(id).name
})
);
this.filterService.addFilterGroup({
slug: 'dossierTemplateFilters',
@ -273,13 +286,16 @@ export class DossierListingScreenComponent
this.filterService.addFilterGroup({
slug: 'quickFilters',
filters: quickFilters,
checker: (dw: DossierWrapper) => quickFilters.reduce((acc, f) => acc || (f.checked && f.checker(dw)), false)
checker: (dw: Dossier) => quickFilters.reduce((acc, f) => acc || (f.checked && f.checker(dw)), false)
});
const dossierFilters = this.entitiesService.all.map<NestedFilter>(dossier => ({
key: dossier.dossierName,
label: dossier.dossierName
}));
const dossierFilters = this.entitiesService.all.map(
dossier =>
new NestedFilter({
id: dossier.dossierName,
label: dossier.dossierName
})
);
this.filterService.addFilterGroup({
slug: 'dossierNameFilter',
label: this._translateService.instant('dossier-listing.filters.label'),
@ -290,30 +306,30 @@ export class DossierListingScreenComponent
});
}
private _createQuickFilters() {
private _createQuickFilters(): NestedFilter[] {
const myDossiersLabel = this._translateService.instant('dossier-listing.quick-filters.my-dossiers');
const filters: NestedFilter[] = [
{
key: 'my-dossiers',
id: 'my-dossiers',
label: myDossiersLabel,
checker: (dw: DossierWrapper) => dw.ownerId === this.currentUser.id
checker: (dw: Dossier) => dw.ownerId === this.currentUser.id
},
{
key: 'to-approve',
id: 'to-approve',
label: this._translateService.instant('dossier-listing.quick-filters.to-approve'),
checker: (dw: DossierWrapper) => dw.approverIds.includes(this.currentUser.id)
checker: (dw: Dossier) => dw.approverIds.includes(this.currentUser.id)
},
{
key: 'to-review',
id: 'to-review',
label: this._translateService.instant('dossier-listing.quick-filters.to-review'),
checker: (dw: DossierWrapper) => dw.memberIds.includes(this.currentUser.id)
checker: (dw: Dossier) => dw.memberIds.includes(this.currentUser.id)
},
{
key: 'other',
id: 'other',
label: this._translateService.instant('dossier-listing.quick-filters.other'),
checker: (dw: DossierWrapper) => !dw.memberIds.includes(this.currentUser.id)
checker: (dw: Dossier) => !dw.memberIds.includes(this.currentUser.id)
}
];
].map(filter => new NestedFilter(filter));
return filters.filter(f => f.label === myDossiersLabel || this._userPreferenceService.areDevFeaturesEnabled);
}

View File

@ -4,6 +4,7 @@
[actionConfigs]="actionConfigs"
[fileAttributeConfigs]="fileAttributeConfigs"
[showCloseButton]="true"
[viewModeSelection]="viewModeSelection"
>
<redaction-file-download-btn
[dossier]="currentDossier"
@ -37,6 +38,7 @@
<div [class.extended]="collapsedDetails" class="content-container">
<iqser-table
(noDataAction)="fileInput.click()"
*ngIf="(listingMode$ | async) === listingModes.table"
[bulkActions]="bulkActions"
[hasScrollButton]="true"
[itemSize]="80"
@ -49,6 +51,23 @@
noDataButtonIcon="red:upload"
noDataIcon="red:document"
></iqser-table>
<iqser-workflow
(addElement)="fileInput.click()"
(noDataAction)="fileInput.click()"
*ngIf="(listingMode$ | async) === listingModes.workflow"
[config]="workflowConfig"
[itemClasses]="{ disabled: disabledFn }"
[itemTemplate]="workflowItemTemplate"
[noDataButtonLabel]="'dossier-overview.no-data.action' | translate"
[noDataText]="'dossier-overview.no-data.title' | translate"
[showNoDataButton]="true"
addElementColumn="UNASSIGNED"
addElementIcon="red:upload"
itemHeight="56px"
noDataButtonIcon="red:upload"
noDataIcon="red:document"
></iqser-workflow>
</div>
<div [class.collapsed]="collapsedDetails" class="right-container" iqserHasScrollbar>
@ -72,95 +91,80 @@
<redaction-dossier-overview-bulk-actions (reload)="bulkActionPerformed()"></redaction-dossier-overview-bulk-actions>
</ng-template>
<ng-template #filenameTemplate let-fileStatus="entity">
<ng-template #filenameTemplate let-file="entity">
<div class="cell">
<div class="filename-wrapper">
<div [class.error]="fileStatus.isError" class="table-item-title text-overflow">
<span [matTooltip]="fileStatus.filename" matTooltipPosition="above">
{{ fileStatus.filename }}
<div>
<div [class.error]="file.isError" [matTooltip]="file.filename" class="table-item-title" matTooltipPosition="above">
{{ file.filename }}
</div>
</div>
<div *ngIf="file.primaryAttribute" class="small-label">
<div class="primary-attribute">
<span [matTooltip]="file.primaryAttribute" matTooltipPosition="above">
{{ file.primaryAttribute }}
</span>
</div>
</div>
<div *ngIf="fileStatus.primaryAttribute" class="small-label">
<div class="primary-attribute text-overflow">
<span [matTooltip]="fileStatus.primaryAttribute" matTooltipPosition="above">
{{ fileStatus.primaryAttribute }}
</span>
</div>
</div>
<div class="small-label stats-subtitle">
<div>
<mat-icon svgIcon="red:pages"></mat-icon>
{{ fileStatus.numberOfPages }}
</div>
<div>
<mat-icon svgIcon="red:exclude-pages"></mat-icon>
{{ fileStatus.excludedPagesCount }}
</div>
<div *ngIf="fileStatus.lastOCRTime" [matTooltipPosition]="'above'" [matTooltip]="'dossier-overview.ocr-performed' | translate">
<mat-icon svgIcon="red:ocr"></mat-icon>
{{ fileStatus.lastOCRTime | date: 'mediumDate' }}
</div>
</div>
<ng-container *ngTemplateOutlet="statsTemplate; context: { entity: file }"></ng-container>
</div>
</ng-template>
<ng-template #addedOnTemplate let-fileStatus="entity">
<ng-template #addedOnTemplate let-file="entity">
<div class="cell">
<div [class.error]="fileStatus.isError" class="small-label">
{{ fileStatus.added | date: 'd MMM. yyyy, hh:mm a' }}
<div [class.error]="file.isError" class="small-label">
{{ file.added | date: 'd MMM. yyyy, hh:mm a' }}
</div>
</div>
</ng-template>
<ng-template #attributeTemplate let-config="extra" let-fileStatus="entity">
<ng-template #attributeTemplate let-config="extra" let-file="entity">
<div class="cell">
{{ fileStatus.fileAttributes.attributeIdToValue[config.id] }}
{{ file.fileAttributes.attributeIdToValue[config.id] }}
</div>
</ng-template>
<ng-template #needsWorkTemplate let-fileStatus="entity">
<ng-template #needsWorkTemplate let-file="entity">
<!-- always show A for error-->
<div *ngIf="fileStatus.isError" class="cell">
<div *ngIf="file.isError" class="cell">
<redaction-annotation-icon color="#dd4d50" label="A" type="square"></redaction-annotation-icon>
</div>
<div *ngIf="!fileStatus.isError" class="cell">
<redaction-needs-work-badge [needsWorkInput]="fileStatus"></redaction-needs-work-badge>
<div *ngIf="!file.isError" class="cell">
<redaction-needs-work-badge [needsWorkInput]="file"></redaction-needs-work-badge>
</div>
</ng-template>
<ng-template #reviewerTemplate let-fileStatus="entity">
<div *ngIf="!fileStatus.isError" class="user-column cell">
<redaction-initials-avatar [userId]="fileStatus.currentReviewer" [withName]="true"></redaction-initials-avatar>
<ng-template #reviewerTemplate let-file="entity">
<div *ngIf="!file.isError" class="user-column cell">
<redaction-initials-avatar [userId]="file.currentReviewer" [withName]="true"></redaction-initials-avatar>
</div>
</ng-template>
<ng-template #pagesTemplate let-fileStatus="entity">
<div *ngIf="!fileStatus.isError" class="cell">
<ng-template #pagesTemplate let-file="entity">
<div *ngIf="!file.isError" class="cell">
<div class="small-label stats-subtitle">
<div>
<mat-icon svgIcon="red:pages"></mat-icon>
{{ fileStatus.numberOfPages }}
{{ file.numberOfPages }}
</div>
</div>
</div>
</ng-template>
<ng-template #statusTemplate let-fileStatus="entity">
<div [class.extend-cols]="fileStatus.isError" class="status-container cell">
<div *ngIf="fileStatus.isError" class="small-label error" translate="dossier-overview.file-listing.file-entry.file-error"></div>
<div *ngIf="fileStatus.isPending" class="small-label" translate="dossier-overview.file-listing.file-entry.file-pending"></div>
<ng-template #statusTemplate let-file="entity">
<div [class.extend-cols]="file.isError" class="status-container cell">
<div *ngIf="file.isError" class="small-label error" translate="dossier-overview.file-listing.file-entry.file-error"></div>
<div *ngIf="file.isPending" class="small-label" translate="dossier-overview.file-listing.file-entry.file-pending"></div>
<div
*ngIf="fileStatus.isProcessing"
*ngIf="file.isProcessing"
class="small-label loading"
translate="dossier-overview.file-listing.file-entry.file-processing"
></div>
<iqser-status-bar
*ngIf="fileStatus.isWorkable"
*ngIf="file.isWorkable"
[configs]="[
{
color: fileStatus.status,
color: file.status,
length: 1
}
]"
@ -168,9 +172,68 @@
<redaction-file-actions
(actionPerformed)="calculateData()"
*ngIf="!fileStatus.isProcessing"
[fileStatus]="fileStatus"
*ngIf="!file.isProcessing"
[file]="file"
class="mr-4"
type="dossier-overview-list"
></redaction-file-actions>
</div>
</ng-template>
<ng-template #viewModeSelection>
<div class="view-mode-selection">
<div class="all-caps-label" translate="view-mode.view-as"></div>
<iqser-circle-button
(action)="listingMode = listingModes.workflow"
[attr.aria-expanded]="(listingMode$ | async) === listingModes.workflow"
[tooltip]="'view-mode.workflow' | translate"
icon="iqser:lanes"
></iqser-circle-button>
<iqser-circle-button
(action)="listingMode = listingModes.table"
[attr.aria-expanded]="(listingMode$ | async) === listingModes.table"
[tooltip]="'view-mode.list' | translate"
icon="iqser:list"
></iqser-circle-button>
</div>
</ng-template>
<ng-template #workflowItemTemplate let-file="entity">
<div class="workflow-item">
<div>
<div class="details">
<div [matTooltip]="file.filename" class="filename" matTooltipPosition="above">
{{ file.filename }}
</div>
<ng-container *ngTemplateOutlet="statsTemplate; context: { entity: file }"></ng-container>
</div>
<div class="user">
<redaction-initials-avatar [userId]="file.currentReviewer"></redaction-initials-avatar>
</div>
</div>
<redaction-file-actions
(actionPerformed)="actionPerformed($event, file)"
*ngIf="!file.isProcessing"
[file]="file"
type="dossier-overview-workflow"
></redaction-file-actions>
</div>
</ng-template>
<ng-template #statsTemplate let-file="entity">
<div class="small-label stats-subtitle">
<div>
<mat-icon svgIcon="red:pages"></mat-icon>
{{ file.numberOfPages }}
</div>
<div>
<mat-icon svgIcon="red:exclude-pages"></mat-icon>
{{ file.excludedPagesCount }}
</div>
<div *ngIf="file.lastOCRTime" [matTooltipPosition]="'above'" [matTooltip]="'dossier-overview.ocr-performed' | translate">
<mat-icon svgIcon="red:ocr"></mat-icon>
{{ file.lastOCRTime | date: 'mediumDate' }}
</div>
</div>
</ng-template>

View File

@ -1,4 +1,5 @@
@use 'variables';
@use 'common-mixins';
.file-upload-input {
display: none;
@ -27,6 +28,7 @@
.primary-attribute {
padding-top: 6px;
@include common-mixins.line-clamp(1);
}
&.extend-cols {
@ -63,3 +65,56 @@
background-color: inherit;
}
}
.view-mode-selection {
border-right: 1px solid variables.$separator;
padding-right: 16px;
margin-right: 16px !important;
display: flex;
align-items: center;
> iqser-circle-button:not(:last-child) {
margin-right: 2px;
}
> div {
margin-right: 8px;
}
}
.workflow-item {
padding: 10px;
> div {
display: flex;
justify-content: space-between;
.details {
max-width: calc(100% - 28px);
.filename {
font-weight: 600;
line-height: 18px;
@include common-mixins.line-clamp(1);
}
}
.user {
display: flex;
align-items: flex-end;
}
}
redaction-file-actions {
margin-top: 10px;
display: none;
}
&:hover redaction-file-actions {
display: block;
}
}
.stats-subtitle {
margin-top: 4px;
}

View File

@ -10,6 +10,7 @@ import {
TemplateRef,
ViewChild
} from '@angular/core';
import { FileStatus, FileStatuses, IFileAttributeConfig } from '@redaction/red-ui-http';
import { AppStateService } from '@state/app-state.service';
import { FileDropOverlayService } from '@upload-download/services/file-drop-overlay.service';
import { FileUploadModel } from '@upload-download/model/file-upload.model';
@ -18,13 +19,13 @@ import { StatusOverlayService } from '@upload-download/services/status-overlay.s
import { TranslateService } from '@ngx-translate/core';
import * as moment from 'moment';
import { DossierDetailsComponent } from '../../components/dossier-details/dossier-details.component';
import { FileStatusWrapper } from '@models/file/file-status.wrapper';
import { File } from '@models/file/file';
import { UserService } from '@services/user.service';
import { timer } from 'rxjs';
import { tap } from 'rxjs/operators';
import { RedactionFilterSorter } from '@utils/sorters/redaction-filter-sorter';
import { StatusSorter } from '@utils/sorters/status-sorter';
import { convertFiles, handleFileDrop } from '@utils/file-drop-utils';
import { convertFiles, Files, handleFileDrop } from '@utils/file-drop-utils';
import { DossiersDialogService } from '../../services/dossiers-dialog.service';
import { OnAttach, OnDetach } from '@utils/custom-route-reuse.strategy';
import { ConfigService } from '@services/config.service';
@ -32,32 +33,36 @@ import { ActionConfig } from '@shared/components/page-header/models/action-confi
import {
CircleButtonTypes,
DefaultListingServices,
INestedFilter,
keyChecker,
ListingComponent,
ListingModes,
LoadingService,
NestedFilter,
TableColumnConfig,
TableComponent,
Toaster
Toaster,
WorkflowConfig
} from '@iqser/common-ui';
import { DossierAttributesService } from '@shared/services/controller-wrappers/dossier-attributes.service';
import { DossierAttributeWithValue } from '@models/dossier-attributes.model';
import { UserPreferenceService } from '@services/user-preference.service';
import { workloadTranslations } from '../../translations/workload-translations';
import { fileStatusTranslations } from '../../translations/file-status-translations';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { annotationFilterChecker } from '@utils/filter-utils';
import { PermissionsService } from '@services/permissions.service';
import { RouterHistoryService } from '@services/router-history.service';
import { FileAttributeConfig } from '@redaction/red-ui-http';
import { DossierWrapper } from '@state/model/dossier.wrapper';
import { Dossier } from '@state/model/dossier';
import { Router } from '@angular/router';
import { FileActionService } from '../../services/file-action.service';
@Component({
templateUrl: './dossier-overview-screen.component.html',
styleUrls: ['./dossier-overview-screen.component.scss'],
providers: [...DefaultListingServices, { provide: ListingComponent, useExisting: forwardRef(() => DossierOverviewScreenComponent) }]
})
export class DossierOverviewScreenComponent extends ListingComponent<FileStatusWrapper> implements OnInit, OnDestroy, OnDetach, OnAttach {
export class DossierOverviewScreenComponent extends ListingComponent<File> implements OnInit, OnDestroy, OnDetach, OnAttach {
readonly listingModes = ListingModes;
readonly circleButtonTypes = CircleButtonTypes;
readonly currentUser = this._userService.currentUser;
currentDossier = this._appStateService.activeDossier;
@ -70,29 +75,30 @@ export class DossierOverviewScreenComponent extends ListingComponent<FileStatusW
hide: !this.currentUser.isManager
}
];
tableColumnConfigs: readonly TableColumnConfig<FileStatusWrapper>[] = [];
tableColumnConfigs: readonly TableColumnConfig<File>[] = [];
collapsedDetails = false;
dossierAttributes: DossierAttributeWithValue[] = [];
fileAttributeConfigs: FileAttributeConfig[];
@ViewChild('filenameTemplate', { static: true }) filenameTemplate: TemplateRef<never>;
@ViewChild('addedOnTemplate', { static: true }) addedOnTemplate: TemplateRef<never>;
@ViewChild('attributeTemplate', { static: true }) attributeTemplate: TemplateRef<never>;
@ViewChild('needsWorkTemplate', { static: true }) needsWorkTemplate: TemplateRef<never>;
@ViewChild('reviewerTemplate', { static: true }) reviewerTemplate: TemplateRef<never>;
@ViewChild('pagesTemplate', { static: true }) pagesTemplate: TemplateRef<never>;
@ViewChild('statusTemplate', { static: true }) statusTemplate: TemplateRef<never>;
protected readonly _primaryKey = 'filename';
fileAttributeConfigs: IFileAttributeConfig[];
@ViewChild('filenameTemplate', { static: true }) filenameTemplate: TemplateRef<unknown>;
@ViewChild('addedOnTemplate', { static: true }) addedOnTemplate: TemplateRef<unknown>;
@ViewChild('attributeTemplate', { static: true }) attributeTemplate: TemplateRef<unknown>;
@ViewChild('needsWorkTemplate', { static: true }) needsWorkTemplate: TemplateRef<unknown>;
@ViewChild('reviewerTemplate', { static: true }) reviewerTemplate: TemplateRef<unknown>;
@ViewChild('pagesTemplate', { static: true }) pagesTemplate: TemplateRef<unknown>;
@ViewChild('statusTemplate', { static: true }) statusTemplate: TemplateRef<unknown>;
readonly workflowConfig: WorkflowConfig<File, FileStatus>;
@ViewChild(DossierDetailsComponent, { static: false })
private readonly _dossierDetailsComponent: DossierDetailsComponent;
private _lastScrolledIndex: number;
@ViewChild('needsWorkFilterTemplate', { read: TemplateRef, static: true })
private readonly _needsWorkFilterTemplate: TemplateRef<unknown>;
@ViewChild('fileInput') private readonly _fileInput: ElementRef;
@ViewChild(TableComponent) private readonly _tableComponent: TableComponent<DossierWrapper>;
@ViewChild(TableComponent) private readonly _tableComponent: TableComponent<Dossier>;
constructor(
private readonly _toaster: Toaster,
protected readonly _injector: Injector,
private readonly _router: Router,
private readonly _userService: UserService,
readonly permissionsService: PermissionsService,
private readonly _loadingService: LoadingService,
@ -104,15 +110,53 @@ export class DossierOverviewScreenComponent extends ListingComponent<FileStatusW
private readonly _changeDetectorRef: ChangeDetectorRef,
private readonly _fileUploadService: FileUploadService,
private readonly _statusOverlayService: StatusOverlayService,
private readonly _userPreferenceService: UserPreferenceService,
private readonly _fileDropOverlayService: FileDropOverlayService,
private readonly _dossierAttributesService: DossierAttributesService
private readonly _dossierAttributesService: DossierAttributesService,
private readonly _fileActionService: FileActionService
) {
super(_injector);
this._loadEntitiesFromState();
this.fileAttributeConfigs = this._appStateService.getFileAttributeConfig(
this.currentDossier.dossierTemplateId
).fileAttributeConfigs;
this.workflowConfig = {
columnIdentifierFn: entity => entity.status,
itemVersionFn: (entity: File) => `${entity.lastUpdated}-${entity.numberOfAnalyses}`,
columns: [
{
label: fileStatusTranslations[FileStatuses.UNASSIGNED],
key: FileStatuses.UNASSIGNED,
enterFn: this.unassignFn,
enterPredicate: (entity: File) => false,
color: '#D3D5DA'
},
{
label: fileStatusTranslations[FileStatuses.UNDER_REVIEW],
enterFn: this.underReviewFn,
enterPredicate: (file: File) =>
this.permissionsService.canSetUnderReview(file) ||
this.permissionsService.canAssignToSelf(file) ||
this.permissionsService.canAssignUser(file),
key: FileStatuses.UNDER_REVIEW,
color: '#FDBD00'
},
{
label: fileStatusTranslations[FileStatuses.UNDER_APPROVAL],
enterFn: this.underApprovalFn,
enterPredicate: (file: File) =>
this.permissionsService.canSetUnderApproval(file) || this.permissionsService.canUndoApproval(file),
key: FileStatuses.UNDER_APPROVAL,
color: '#374C81'
},
{
label: fileStatusTranslations[FileStatuses.APPROVED],
enterFn: this.approveFn,
enterPredicate: (file: File) => this.permissionsService.isReadyForApproval(file),
key: FileStatuses.APPROVED,
color: '#48C9F7'
}
]
};
}
get checkedRequiredFilters() {
@ -127,11 +171,44 @@ export class DossierOverviewScreenComponent extends ListingComponent<FileStatusW
return this.fileAttributeConfigs.filter(config => config.displayedInFileList);
}
routerLinkFn = (fileStatus: FileStatusWrapper) =>
fileStatus.canBeOpened ? [`/main/dossiers/${this.currentDossier.dossierId}/file/${fileStatus.fileId}`] : [];
unassignFn = async (file: File) => {
// TODO
console.log('unassign', file);
};
disabledFn = (fileStatus: FileStatusWrapper) => fileStatus.excluded;
lastOpenedFn = (fileStatus: FileStatusWrapper) => fileStatus.lastOpened;
underReviewFn = (file: File) => {
this._fileActionService.assignFile('reviewer', null, file, () => this._loadingService.loadWhile(this.reloadDossiers()), true);
};
underApprovalFn = async (file: File) => {
if (this._appStateService.activeDossier.approverIds.length > 1) {
this._fileActionService.assignFile('approver', null, file, () => this._loadingService.loadWhile(this.reloadDossiers()), true);
} else {
this._loadingService.start();
await this._fileActionService.setFilesUnderApproval([file]).toPromise();
await this.reloadDossiers();
this._loadingService.stop();
}
};
approveFn = async (file: File) => {
this._loadingService.start();
await this._fileActionService.setFilesApproved([file]).toPromise();
await this.reloadDossiers();
this._loadingService.stop();
};
actionPerformed(action?: string, file?: File) {
this.calculateData();
if (action === 'navigate') {
this._router.navigate([file.routerLink]);
}
}
disabledFn = (fileStatus: File) => fileStatus.excluded;
lastOpenedFn = (fileStatus: File) => fileStatus.lastOpened;
async ngOnInit(): Promise<void> {
this._configureTableColumns();
@ -160,8 +237,6 @@ export class DossierOverviewScreenComponent extends ListingComponent<FileStatusW
.pipe(tap(index => (this._lastScrolledIndex = index)))
.subscribe();
this.searchService.setSearchKey('filename');
this.dossierAttributes = await this._dossierAttributesService.getValues(this.currentDossier);
} catch (e) {
} finally {
@ -213,39 +288,39 @@ export class DossierOverviewScreenComponent extends ListingComponent<FileStatusW
}
@HostListener('drop', ['$event'])
onDrop(event: DragEvent) {
onDrop(event: DragEvent): void {
handleFileDrop(event, this.currentDossier, this._uploadFiles.bind(this));
}
@HostListener('dragover', ['$event'])
onDragOver(event) {
onDragOver(event): void {
event.stopPropagation();
event.preventDefault();
}
async uploadFiles(files: File[] | FileList) {
async uploadFiles(files: Files): Promise<void> {
await this._uploadFiles(convertFiles(files, this.currentDossier));
this._fileInput.nativeElement.value = null;
}
async bulkActionPerformed() {
async bulkActionPerformed(): Promise<void> {
this.entitiesService.setSelected([]);
await this.reloadDossiers();
}
openEditDossierDialog($event: MouseEvent) {
this._dialogService.openDialog('editDossier', $event, {
dossierWrapper: this.currentDossier
dossier: this.currentDossier
});
}
openAssignDossierMembersDialog(): void {
const data = { dossierWrapper: this.currentDossier, section: 'members' };
const data = { dossier: this.currentDossier, section: 'members' };
this._dialogService.openDialog('editDossier', null, data, async () => await this.reloadDossiers());
}
openDossierDictionaryDialog() {
const data = { dossierWrapper: this.currentDossier, section: 'dossierDictionary' };
const data = { dossier: this.currentDossier, section: 'dossierDictionary' };
this._dialogService.openDialog('editDossier', null, data, async () => {
await this.reloadDossiers();
});
@ -255,11 +330,11 @@ export class DossierOverviewScreenComponent extends ListingComponent<FileStatusW
this.collapsedDetails = !this.collapsedDetails;
}
recentlyModifiedChecker = (file: FileStatusWrapper) =>
recentlyModifiedChecker = (file: File) =>
moment(file.lastUpdated).add(this._configService.values.RECENT_PERIOD_IN_HOURS, 'hours').isAfter(moment());
private _configureTableColumns() {
const dynamicColumns: TableColumnConfig<FileStatusWrapper>[] = [];
const dynamicColumns: TableColumnConfig<File>[] = [];
for (const config of this.displayedInFileListAttributes) {
if (config.displayedInFileList) {
dynamicColumns.push({ label: config.label, notTranslatable: true, template: this.attributeTemplate, extra: config });
@ -268,7 +343,7 @@ export class DossierOverviewScreenComponent extends ListingComponent<FileStatusW
this.tableColumnConfigs = [
{
label: _('dossier-overview.table-col-names.name'),
sortByKey: 'filename',
sortByKey: 'searchKey',
template: this.filenameTemplate,
width: '3fr'
},
@ -292,7 +367,7 @@ export class DossierOverviewScreenComponent extends ListingComponent<FileStatusW
},
{
label: _('dossier-overview.table-col-names.pages'),
sortByKey: 'pages',
sortByKey: 'numberOfPages',
template: this.pagesTemplate
},
{
@ -323,7 +398,7 @@ export class DossierOverviewScreenComponent extends ListingComponent<FileStatusW
return;
}
const allDistinctFileStatusWrapper = new Set<string>();
const allDistinctFileStatuses = new Set<string>();
const allDistinctPeople = new Set<string>();
const allDistinctAddedDates = new Set<string>();
const allDistinctNeedsWork = new Set<string>();
@ -332,7 +407,7 @@ export class DossierOverviewScreenComponent extends ListingComponent<FileStatusW
this.entitiesService.all.forEach(file => {
allDistinctPeople.add(file.currentReviewer);
allDistinctFileStatusWrapper.add(file.status);
allDistinctFileStatuses.add(file.status);
allDistinctAddedDates.add(moment(file.added).format('DD/MM/YYYY'));
if (file.analysisRequired) {
@ -379,33 +454,40 @@ export class DossierOverviewScreenComponent extends ListingComponent<FileStatusW
});
});
const statusFilters = [...allDistinctFileStatusWrapper].map<NestedFilter>(item => ({
key: item,
label: this._translateService.instant(fileStatusTranslations[item])
}));
const statusFilters = [...allDistinctFileStatuses].map(
status =>
new NestedFilter({
id: status,
label: this._translateService.instant(fileStatusTranslations[status])
})
);
this.filterService.addFilterGroup({
slug: 'statusFilters',
label: this._translateService.instant('filters.status'),
icon: 'red:status',
filters: statusFilters.sort(StatusSorter.byStatus),
filters: statusFilters.sort((a, b) => StatusSorter[a.id] - StatusSorter[b.id]),
checker: keyChecker('status')
});
const peopleFilters = [];
const peopleFilters: NestedFilter[] = [];
if (allDistinctPeople.has(undefined) || allDistinctPeople.has(null)) {
allDistinctPeople.delete(undefined);
allDistinctPeople.delete(null);
peopleFilters.push({
key: null,
label: this._translateService.instant('initials-avatar.unassigned')
});
peopleFilters.push(
new NestedFilter({
id: null,
label: this._translateService.instant('initials-avatar.unassigned')
})
);
}
allDistinctPeople.forEach(userId => {
peopleFilters.push({
key: userId,
label: this._userService.getNameForId(userId)
});
peopleFilters.push(
new NestedFilter({
id: userId,
label: this._userService.getNameForId(userId)
})
);
});
this.filterService.addFilterGroup({
slug: 'peopleFilters',
@ -415,10 +497,13 @@ export class DossierOverviewScreenComponent extends ListingComponent<FileStatusW
checker: keyChecker('currentReviewer')
});
const needsWorkFilters = [...allDistinctNeedsWork].map<NestedFilter>(item => ({
key: item,
label: workloadTranslations[item]
}));
const needsWorkFilters = [...allDistinctNeedsWork].map(
item =>
new NestedFilter({
id: item,
label: workloadTranslations[item]
})
);
this.filterService.addFilterGroup({
slug: 'needsWorkFilters',
@ -437,27 +522,33 @@ export class DossierOverviewScreenComponent extends ListingComponent<FileStatusW
slug: key,
label: key,
icon: 'red:template',
filters: [...filterValue].map<NestedFilter>((value: string) => ({
key: value,
label: value === '-' ? this._translateService.instant('filters.empty') : value
})),
checker: (input: FileStatusWrapper, filter: NestedFilter) => filter.key === input.fileAttributes.attributeIdToValue[id]
filters: [...filterValue].map(
(value: string) =>
new NestedFilter({
id: value,
label: value === '-' ? this._translateService.instant('filters.empty') : value
})
),
checker: (input: File, filter: INestedFilter) => filter.id === input.fileAttributes.attributeIdToValue[id]
});
});
this.filterService.addFilterGroup({
slug: 'quickFilters',
filters: this._createQuickFilters(),
checker: (file: FileStatusWrapper) =>
checker: (file: File) =>
this.checkedRequiredFilters.reduce((acc, f) => acc && f.checker(file), true) &&
(this.checkedNotRequiredFilters.length === 0 ||
this.checkedNotRequiredFilters.reduce((acc, f) => acc || f.checker(file), false))
});
const filesNamesFilters = this.entitiesService.all.map<NestedFilter>(file => ({
key: file.filename,
label: file.filename
}));
const filesNamesFilters = this.entitiesService.all.map(
file =>
new NestedFilter({
id: file.filename,
label: file.filename
})
);
this.filterService.addFilterGroup({
slug: 'filesNamesFilter',
@ -469,13 +560,13 @@ export class DossierOverviewScreenComponent extends ListingComponent<FileStatusW
});
}
private _createQuickFilters() {
let quickFilters = [];
private _createQuickFilters(): NestedFilter[] {
let quickFilters: INestedFilter[] = [];
if (this.entitiesService.all.filter(this.recentlyModifiedChecker).length > 0) {
const recentPeriod = this._configService.values.RECENT_PERIOD_IN_HOURS;
quickFilters = [
{
key: 'recent',
id: 'recent',
label: this._translateService.instant('dossier-overview.quick-filters.recent', {
hours: recentPeriod
}),
@ -488,20 +579,20 @@ export class DossierOverviewScreenComponent extends ListingComponent<FileStatusW
return [
...quickFilters,
{
key: 'assigned-to-me',
id: 'assigned-to-me',
label: this._translateService.instant('dossier-overview.quick-filters.assigned-to-me'),
checker: (file: FileStatusWrapper) => file.currentReviewer === this.currentUser.id
checker: (file: File) => file.currentReviewer === this.currentUser.id
},
{
key: 'unassigned',
id: 'unassigned',
label: this._translateService.instant('dossier-overview.quick-filters.unassigned'),
checker: (file: FileStatusWrapper) => !file.currentReviewer
checker: (file: File) => !file.currentReviewer
},
{
key: 'assigned-to-others',
id: 'assigned-to-others',
label: this._translateService.instant('dossier-overview.quick-filters.assigned-to-others'),
checker: (file: FileStatusWrapper) => !!file.currentReviewer && file.currentReviewer !== this.currentUser.id
checker: (file: File) => !!file.currentReviewer && file.currentReviewer !== this.currentUser.id
}
];
].map(filter => new NestedFilter(filter));
}
}

View File

@ -92,6 +92,7 @@
(actionPerformed)="fileActionPerformed($event)"
[activeDocumentInfo]="viewDocumentInfo"
[activeExcludePages]="excludePages"
type="file-preview"
></redaction-file-actions>
<iqser-circle-button
@ -107,10 +108,10 @@
(action)="downloadOriginalFile()"
*ngIf="userPreferenceService.areDevFeaturesEnabled"
[tooltip]="'file-preview.download-original-file' | translate"
[type]="circleButtonTypes.primary"
class="ml-8"
icon="red:download"
tooltipPosition="below"
[type]="circleButtonTypes.primary"
></iqser-circle-button>
<!-- End Dev Mode Features-->
@ -141,7 +142,7 @@
[annotations]="annotations"
[canPerformActions]="canPerformAnnotationActions"
[fileData]="displayData"
[fileStatus]="appStateService.activeFile"
[file]="appStateService.activeFile"
[multiSelectActive]="multiSelectActive"
[shouldDeselectAnnotationsOnPageChange]="shouldDeselectAnnotationsOnPageChange"
></redaction-pdf-viewer>
@ -158,7 +159,7 @@
<redaction-document-info
(closeDocumentInfoView)="viewDocumentInfo = false"
*ngIf="viewDocumentInfo"
[file]="fileData.fileStatus"
[file]="fileData.file"
></redaction-document-info>
<redaction-file-workload

View File

@ -8,8 +8,8 @@ import {
CircleButtonTypes,
Debounce,
FilterService,
INestedFilter,
LoadingService,
NestedFilter,
processFilters,
Toaster
} from '@iqser/common-ui';
@ -21,17 +21,12 @@ import { AnnotationData, FileDataModel } from '@models/file/file-data.model';
import { FileActionService } from '../../services/file-action.service';
import { AnnotationDrawService } from '../../services/annotation-draw.service';
import { AnnotationProcessingService } from '../../services/annotation-processing.service';
import { FileStatusWrapper } from '@models/file/file-status.wrapper';
import { File } from '@models/file/file';
import { PermissionsService } from '@services/permissions.service';
import { timer } from 'rxjs';
import { UserPreferenceService } from '@services/user-preference.service';
import { UserService, UserWrapper } from '@services/user.service';
import {
FileManagementControllerService,
FileStatus,
StatusControllerService,
UserPreferenceControllerService
} from '@redaction/red-ui-http';
import { UserService } from '@services/user.service';
import { FileManagementControllerService, FileStatus, List, UserPreferenceControllerService } from '@redaction/red-ui-http';
import { PdfViewerDataService } from '../../services/pdf-viewer-data.service';
import { download } from '@utils/file-download-utils';
import { ViewMode } from '@models/file/view-mode';
@ -44,6 +39,8 @@ import { fileStatusTranslations } from '../../translations/file-status-translati
import { handleFilterDelta } from '@utils/filter-utils';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { FileActionsComponent } from '../../components/file-actions/file-actions.component';
import { User } from '@models/user';
import { FilesService } from '../../services/files.service';
import Annotation = Core.Annotations.Annotation;
const ALL_HOTKEY_ARRAY = ['Escape', 'F', 'f'];
@ -71,6 +68,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
viewDocumentInfo = false;
excludePages = false;
@ViewChild(PdfViewerComponent) readonly viewerComponent: PdfViewerComponent;
@ViewChild('fileActions') fileActions: FileActionsComponent;
private _instance: WebViewerInstance;
private _lastPage: string;
private _reloadFileOnReanalysis = false;
@ -79,8 +77,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
read: TemplateRef,
static: true
})
private readonly _filterTemplate: TemplateRef<NestedFilter>;
@ViewChild('fileActions') fileActions: FileActionsComponent;
private readonly _filterTemplate: TemplateRef<INestedFilter>;
constructor(
readonly appStateService: AppStateService,
@ -97,7 +94,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
private readonly _annotationDrawService: AnnotationDrawService,
private readonly _fileActionService: FileActionService,
private readonly _fileDownloadService: PdfViewerDataService,
private readonly _statusControllerService: StatusControllerService,
private readonly _filesService: FilesService,
private readonly _ngZone: NgZone,
private readonly _fileManagementControllerService: FileManagementControllerService,
private readonly _loadingService: LoadingService,
@ -113,7 +110,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
});
}
get singleUsersSelectOptions(): string[] {
get singleUsersSelectOptions(): List {
return this.appStateService.activeFile?.isUnderApproval
? this.appStateService.activeDossier.approverIds
: this.appStateService.activeDossier.memberIds;
@ -146,11 +143,11 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
}
get canSwitchToRedactedView(): boolean {
return this.fileData && !this.fileData.fileStatus.analysisRequired && !this.fileData.fileStatus.excluded;
return this.fileData && !this.fileData.file.analysisRequired && !this.fileData.file.excluded;
}
get canSwitchToDeltaView(): boolean {
return this.fileData?.redactionChangeLog?.redactionLogEntry?.length > 0 && !this.fileData.fileStatus.excluded;
return this.fileData?.redactionChangeLog?.redactionLogEntry?.length > 0 && !this.fileData.file.excluded;
}
get canAssign(): boolean {
@ -174,7 +171,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
}
get lastReviewer(): string | undefined {
return this.appStateService.activeFile.fileStatus.lastReviewer;
return this.appStateService.activeFile.lastReviewer;
}
get assignOrChangeReviewerTooltip(): string {
@ -187,11 +184,11 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
return this.appStateService.activeFile.currentReviewer;
}
get status(): FileStatus.StatusEnum {
get status(): FileStatus {
return this.appStateService.activeFile.status;
}
get statusBarConfig(): [{ length: number; color: FileStatus.StatusEnum }] {
get statusBarConfig(): [{ length: number; color: FileStatus }] {
return [{ length: 1, color: this.status }];
}
@ -200,9 +197,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
}
get canAssignReviewer(): boolean {
return (
!this.currentReviewer && this.permissionsService.canAssignUser() && this.appStateService.activeDossier.hasMoreThanOneReviewer
);
return !this.currentReviewer && this.permissionsService.canAssignUser() && this.appStateService.activeDossier.hasReviewers;
}
updateViewMode(): void {
@ -261,7 +256,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
this._updateCanPerformActions();
this._subscribeToFileUpdates();
if (this.fileData?.fileStatus?.analysisRequired) {
if (this.fileData?.file?.analysisRequired) {
this.fileActions.reanalyseFile();
}
}
@ -355,7 +350,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
response.manualRedactionEntryWrapper.rectId
);
this._instance.Core.annotationManager.deleteAnnotation(annotation);
this.fileData.fileStatus = await this.appStateService.reloadActiveFile();
this.fileData.file = await this.appStateService.reloadActiveFile();
const distinctPages = entryWrapper.manualRedactionEntry.positions
.map(p => p.page)
.filter((item, pos, self) => self.indexOf(item) === pos);
@ -490,18 +485,18 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
}
async assignToMe() {
await this._fileActionService.assignToMe(this.fileData.fileStatus, async () => {
await this._fileActionService.assignToMe([this.fileData.file], async () => {
await this.appStateService.reloadActiveFile();
this._updateCanPerformActions();
});
}
async assignReviewer(user: UserWrapper | string) {
async assignReviewer(user: User | string) {
const reviewerId = typeof user === 'string' ? user : user.id;
const reviewerName = this.userService.getNameForId(reviewerId);
const { dossierId, fileId, filename } = this.fileData.fileStatus;
await this._statusControllerService.setFileReviewer(dossierId, fileId, reviewerId).toPromise();
const { dossierId, fileId, filename } = this.fileData.file;
await this._filesService.setReviewerFor([fileId], dossierId, reviewerId).toPromise();
this._toaster.info(_('assignment.reviewer'), { params: { reviewerName, filename } });
await this.appStateService.reloadActiveFile();
@ -523,9 +518,9 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
downloadOriginalFile() {
this.addSubscription = this._fileManagementControllerService
.downloadOriginalFile(this.dossierId, this.fileId, true, this.fileData.fileStatus.cacheIdentifier, 'response')
.downloadOriginalFile(this.dossierId, this.fileId, true, this.fileData.file.cacheIdentifier, 'response')
.subscribe(data => {
download(data, this.fileData.fileStatus.filename);
download(data, this.fileData.file.filename);
});
}
@ -561,7 +556,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
if (excludedPages && excludedPages.length > 0) {
const pdfNet = this._instance.Core.PDFNet;
const document = await this._instance.Core.documentViewer.getDocument().getPDFDoc();
await clearStamps(document, pdfNet, [...Array(this.fileData.fileStatus.numberOfPages).keys()]);
await clearStamps(document, pdfNet, [...Array(this.fileData.file.numberOfPages).keys()]);
await stampPDFPage(
document,
pdfNet,
@ -577,7 +572,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
}
private async _stampExcludedPages() {
await this._doStampExcludedPages(this.fileData.fileStatus.excludedPages);
await this._doStampExcludedPages(this.fileData.file.excludedPages);
this._instance.Core.documentViewer.refreshAll();
this._instance.Core.documentViewer.updateView([this.activeViewerPage], this.activeViewerPage);
this._changeDetectorRef.detectChanges();
@ -585,8 +580,8 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
private _subscribeToFileUpdates(): void {
this.addSubscription = timer(0, 5000).subscribe(async () => await this.appStateService.reloadActiveFile());
this.addSubscription = this.appStateService.fileReanalysed$.subscribe(async (fileStatus: FileStatusWrapper) => {
if (fileStatus.fileId === this.fileId) {
this.addSubscription = this.appStateService.fileReanalysed$.subscribe(async (file: File) => {
if (file.fileId === this.fileId) {
await this._loadFileData(!this._reloadFileOnReanalysis);
this._reloadFileOnReanalysis = false;
this._loadingService.stop();
@ -606,11 +601,11 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
private async _loadFileData(performUpdate = false): Promise<void> {
const fileData = await this._fileDownloadService.loadActiveFileData().toPromise();
if (!fileData.fileStatus?.isPending && !fileData.fileStatus?.isError) {
if (!fileData.file?.isPending && !fileData.file?.isError) {
if (performUpdate) {
this.fileData.redactionLog = fileData.redactionLog;
this.fileData.redactionChangeLog = fileData.redactionChangeLog;
this.fileData.fileStatus = fileData.fileStatus;
this.fileData.file = fileData.file;
this.rebuildFilters(true);
} else {
this.fileData = fileData;
@ -620,7 +615,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
return;
}
if (fileData.fileStatus.isError) {
if (fileData.file.isError) {
await this._router.navigate(['/main/dossiers/' + this.dossierId]);
}
}
@ -648,7 +643,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
private async _cleanupAndRedrawManualAnnotationsForEntirePage(page: number) {
const currentPageAnnotations = this.annotations.filter(a => a.pageNumber === page);
const currentPageAnnotationIds = currentPageAnnotations.map(a => a.id);
this.fileData.fileStatus = await this.appStateService.reloadActiveFile();
this.fileData.file = await this.appStateService.reloadActiveFile();
this._fileDownloadService.loadActiveFileRedactionLog().subscribe(redactionLogPreview => {
this.fileData.redactionLog = redactionLogPreview;

View File

@ -1,23 +1,29 @@
import { Component, forwardRef, Injector, OnDestroy, OnInit, TemplateRef, ViewChild } from '@angular/core';
import { DefaultListingServices, keyChecker, Listable, ListingComponent, LoadingService, TableColumnConfig } from '@iqser/common-ui';
import { MatchedDocument, SearchControllerService, SearchResult } from '@redaction/red-ui-http';
import {
DefaultListingServices,
IListable,
keyChecker,
ListingComponent,
LoadingService,
NestedFilter,
TableColumnConfig
} from '@iqser/common-ui';
import { List, MatchedDocument, SearchControllerService, SearchResult } from '@redaction/red-ui-http';
import { BehaviorSubject, Observable } from 'rxjs';
import { debounceTime, map, skip, switchMap, tap } from 'rxjs/operators';
import { ActivatedRoute, Router } from '@angular/router';
import { AppStateService } from '@state/app-state.service';
import { FileStatusWrapper } from '@models/file/file-status.wrapper';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { fileStatusTranslations } from '../../translations/file-status-translations';
import { SearchPositions } from '@shared/components/page-header/models/search-positions.type';
import { DossierWrapper } from '@state/model/dossier.wrapper';
import { TranslateService } from '@ngx-translate/core';
import { RouterHistoryService } from '@services/router-history.service';
interface ListItem extends Listable {
interface ListItem extends IListable {
readonly dossierId: string;
readonly filename: string;
readonly unmatched: readonly string[] | null;
readonly highlights: Record<string, readonly string[]>;
readonly unmatched: List | null;
readonly highlights: Record<string, List>;
readonly routerLink: string;
readonly status: string;
readonly dossierName: string;
@ -26,7 +32,7 @@ interface ListItem extends Listable {
interface SearchInput {
readonly query: string;
readonly dossierIds?: readonly string[];
readonly dossierIds?: List;
}
@Component({
@ -53,7 +59,6 @@ export class SearchScreenComponent extends ListingComponent<ListItem> implements
tap(result => this.entitiesService.setEntities(result)),
tap(() => this._loadingService.stop())
);
protected readonly _primaryKey = 'filename';
constructor(
private readonly _router: Router,
@ -72,11 +77,14 @@ export class SearchScreenComponent extends ListingComponent<ListItem> implements
label: this._translateService.instant('search-screen.filters.by-dossier'),
filterceptionPlaceholder: this._translateService.instant('search-screen.filters.search-placeholder'),
icon: 'red:folder',
filters: this._appStateService.allDossiers.map(dossier => ({
key: dossier.dossierId,
label: dossier.dossierName
})),
checker: keyChecker('dossierId')
filters: this._appStateService.allDossiers.map(
dossier =>
new NestedFilter({
id: dossier.id,
label: dossier.dossierName
})
),
checker: keyChecker('id')
});
this.addSubscription = _activatedRoute.queryParamMap
@ -89,17 +97,11 @@ export class SearchScreenComponent extends ListingComponent<ListItem> implements
this.addSubscription = this.searchService.valueChanges$.pipe(debounceTime(300)).subscribe(value => this.updateNavigation(value));
this.addSubscription = this.filterService.filterGroups$.pipe(skip(1)).subscribe(group => {
const dossierIds = group[0].filters.filter(v => v.checked).map(v => v.key);
const dossierIds = group[0].filters.filter(v => v.checked).map(v => v.id);
this.search$.next({ query: this.searchService.searchValue, dossierIds: dossierIds });
});
}
routerLinkFn = (entity: ListItem) => [entity.routerLink];
setInitialConfig(): void {
return;
}
updateNavigation(query: string, mustContain?: string): void {
const newQuery = query?.replace(mustContain, `"${mustContain}"`);
const queryParams = newQuery && newQuery !== '' ? { query: newQuery } : {};
@ -137,14 +139,6 @@ export class SearchScreenComponent extends ListingComponent<ListItem> implements
this.search$.next({ query, dossierIds: dossierId ? [dossierId] : [] });
}
private _getFileWrapper(dossierId: string, fileId: string): FileStatusWrapper {
return this._appStateService.getFileById(dossierId, fileId);
}
private _getDossierWrapper(dossierId: string): DossierWrapper {
return this._appStateService.getDossierById(dossierId);
}
private _toMatchedDocuments({ matchedDocuments }: SearchResult): MatchedDocument[] {
return matchedDocuments.filter(doc => doc.score > 0 && doc.matchedTerms.length > 0);
}
@ -154,8 +148,8 @@ export class SearchScreenComponent extends ListingComponent<ListItem> implements
}
private _toListItem({ dossierId, fileId, unmatchedTerms, highlights }: MatchedDocument): ListItem {
const fileWrapper = this._getFileWrapper(dossierId, fileId);
if (!fileWrapper) {
const file = this._appStateService.getFileById(dossierId, fileId);
if (!file) {
return undefined;
}
@ -164,10 +158,11 @@ export class SearchScreenComponent extends ListingComponent<ListItem> implements
dossierId,
unmatched: unmatchedTerms || null,
highlights,
status: fileWrapper.status,
numberOfPages: fileWrapper.numberOfPages,
dossierName: this._getDossierWrapper(dossierId).dossierName,
filename: fileWrapper.filename,
status: file.status,
numberOfPages: file.numberOfPages,
dossierName: this._appStateService.getDossierById(dossierId).dossierName,
filename: file.filename,
searchKey: file.filename,
routerLink: `/main/dossiers/${dossierId}/file/${fileId}`
};
}

View File

@ -1,38 +1,36 @@
import { Injectable } from '@angular/core';
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { SuperTypeSorter } from '@utils/sorters/super-type-sorter';
import { handleCheckedValue, NestedFilter } from '@iqser/common-ui';
import { Filter, handleCheckedValue, IFilter, INestedFilter, NestedFilter } from '@iqser/common-ui';
import { annotationTypesTranslations } from '../../../translations/annotation-types-translations';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
@Injectable()
export class AnnotationProcessingService {
static get secondaryAnnotationFilters(): NestedFilter[] {
static get secondaryAnnotationFilters(): INestedFilter[] {
return [
{
key: 'with-comments',
id: 'with-comments',
icon: 'red:comment',
label: _('filter-menu.with-comments'),
checked: false,
topLevelFilter: true,
children: [],
checker: (annotation: AnnotationWrapper) => annotation?.comments?.length > 0
},
{
key: 'with-reason-changes',
id: 'with-reason-changes',
icon: 'red:reason',
label: _('filter-menu.with-reason-changes'),
checked: false,
topLevelFilter: true,
children: [],
checker: (annotation: AnnotationWrapper) => annotation?.legalBasisChangeValue?.length > 0
}
];
].map(item => new NestedFilter(item));
}
getAnnotationFilter(annotations: AnnotationWrapper[]): NestedFilter[] {
const filterMap = new Map<string, NestedFilter>();
const filters: NestedFilter[] = [];
getAnnotationFilter(annotations: AnnotationWrapper[]): INestedFilter[] {
const filterMap = new Map<string, INestedFilter>();
const filters: INestedFilter[] = [];
annotations?.forEach(a => {
const topLevelFilter = a.superType !== 'hint' && a.superType !== 'redaction' && a.superType !== 'recommendation';
@ -49,21 +47,20 @@ export class AnnotationProcessingService {
if (!parentFilter) {
parentFilter = this._createParentFilter(a.superType, filterMap, filters);
}
const childFilter = {
key: a.type,
const childFilter: IFilter = {
id: a.type,
label: a.type,
checked: false,
filters: [],
matches: 1
};
filterMap.set(key, childFilter);
parentFilter.children.push(childFilter);
parentFilter.children.push(new Filter(childFilter));
}
}
});
for (const filter of filters) {
filter.children.sort((a, b) => a.key.localeCompare(b.key));
filter.children.sort((a, b) => a.id.localeCompare(b.id));
handleCheckedValue(filter);
if (filter.checked || filter.indeterminate) {
filter.expanded = true;
@ -73,13 +70,13 @@ export class AnnotationProcessingService {
}
}
return filters.sort((a, b) => SuperTypeSorter[a.key] - SuperTypeSorter[b.key]);
return filters.sort((a, b) => SuperTypeSorter[a.id] - SuperTypeSorter[b.id]);
}
filterAndGroupAnnotations(
annotations: AnnotationWrapper[],
primaryFilters: NestedFilter[],
secondaryFilters?: NestedFilter[]
primaryFilters: INestedFilter[],
secondaryFilters?: INestedFilter[]
): Map<number, AnnotationWrapper[]> {
const obj = new Map<number, AnnotationWrapper[]>();
@ -116,21 +113,20 @@ export class AnnotationProcessingService {
return obj;
}
private _createParentFilter(key: string, filterMap: Map<string, NestedFilter>, filters: NestedFilter[]) {
const filter: NestedFilter = {
key: key,
private _createParentFilter(key: string, filterMap: Map<string, INestedFilter>, filters: INestedFilter[]) {
const filter: INestedFilter = new NestedFilter({
id: key,
topLevelFilter: true,
matches: 1,
label: annotationTypesTranslations[key],
children: []
};
label: annotationTypesTranslations[key]
});
filterMap.set(key, filter);
filters.push(filter);
return filter;
}
private _getFlatFilters(filters: NestedFilter[], filterBy?: (f: NestedFilter) => boolean) {
const flatFilters: NestedFilter[] = [];
private _getFlatFilters(filters: INestedFilter[], filterBy?: (f: INestedFilter) => boolean) {
const flatFilters: INestedFilter[] = [];
filters.forEach(filter => {
flatFilters.push(filter);
@ -140,7 +136,7 @@ export class AnnotationProcessingService {
return filterBy ? flatFilters.filter(f => filterBy(f)) : flatFilters;
}
private _matchesOne = (filters: NestedFilter[], condition: (filter: NestedFilter) => boolean): boolean => {
private _matchesOne = (filters: INestedFilter[], condition: (filter: INestedFilter) => boolean): boolean => {
if (filters.length === 0) {
return true;
}
@ -154,7 +150,7 @@ export class AnnotationProcessingService {
return false;
};
private _matchesAll = (filters: NestedFilter[], condition: (filter: NestedFilter) => boolean): boolean => {
private _matchesAll = (filters: INestedFilter[], condition: (filter: INestedFilter) => boolean): boolean => {
if (filters.length === 0) {
return true;
}
@ -168,11 +164,11 @@ export class AnnotationProcessingService {
return true;
};
private _checkByFilterKey = (filter: NestedFilter, annotation: AnnotationWrapper) => {
private _checkByFilterKey = (filter: INestedFilter, annotation: AnnotationWrapper) => {
const superType = annotation.superType;
const isNotTopLevelFilter = superType === 'hint' || superType === 'redaction' || superType === 'recommendation';
return filter.key === superType || (filter.key === annotation.type && isNotTopLevelFilter);
return filter.id === superType || (filter.id === annotation.type && isNotTopLevelFilter);
};
private _sortAnnotations(annotations: AnnotationWrapper[]): AnnotationWrapper[] {

View File

@ -1,33 +1,90 @@
import { Injectable } from '@angular/core';
import { Dossier, DossierControllerService } from '@redaction/red-ui-http';
import { Injectable, Injector } from '@angular/core';
import { IDossier } from '@redaction/red-ui-http';
import { EntitiesService, List, QueryParam } from '@iqser/common-ui';
import { Dossier } from '@state/model/dossier';
import { filter, map } from 'rxjs/operators';
import { TEMPORARY_INJECTOR } from './injector';
import { BehaviorSubject, Observable } from 'rxjs';
import { ActivationEnd, Router } from '@angular/router';
import { BaseScreenComponent } from '@components/base-screen/base-screen.component';
export interface IDossiersStats {
totalPeople: number;
totalAnalyzedPages: number;
}
const getRelatedEvents = filter(event => event instanceof ActivationEnd && event.snapshot.component !== BaseScreenComponent);
@Injectable({
providedIn: 'root'
})
export class DossiersService {
constructor(private readonly _dossierControllerService: DossierControllerService) {}
export class DossiersService extends EntitiesService<Dossier, IDossier> {
readonly stats$ = this.all$.pipe(map(entities => this._computeStats(entities)));
readonly activeDossierId$: Observable<string | null>;
readonly activeDossier$: Observable<Dossier | null>;
private readonly _activeDossierId$ = new BehaviorSubject<string | null>(null);
createOrUpdate(dossier: Dossier): Promise<Dossier> {
return this._dossierControllerService.createOrUpdateDossier(dossier).toPromise();
constructor(protected readonly _injector: Injector, private readonly _router: Router) {
super(TEMPORARY_INJECTOR(_injector), 'dossier');
this.activeDossierId$ = this._activeDossierId$.asObservable();
this.activeDossier$ = this.activeDossierId$.pipe(map(id => this.all.find(dossier => dossier.id === id)));
_router.events.pipe(getRelatedEvents).subscribe((event: ActivationEnd) => {
const dossierId = event.snapshot.paramMap.get('dossierId');
const sameIdAsCurrentActive = dossierId === this._activeDossierId$.getValue();
if (sameIdAsCurrentActive) {
return;
}
if (dossierId === null || dossierId === undefined) {
return this._activeDossierId$.next(null);
}
// const notFound = !this.all.some(dossier => dossier.id === dossierId);
// if (notFound) {
// return this._router.navigate(['/main/dossiers']).then();
// }
this._activeDossierId$.next(dossierId);
});
}
delete(dossierId: string): Promise<unknown> {
return this._dossierControllerService.deleteDossier(dossierId).toPromise();
get(): Observable<IDossier[]>;
get(dossierId: string): Observable<IDossier>;
get(dossierId?: string): Observable<IDossier | IDossier[]> {
return dossierId ? super._getOne([dossierId]) : super.getAll();
}
getAll(): Promise<Dossier[]> {
return this._dossierControllerService.getDossiers().toPromise();
createOrUpdate(dossier: IDossier): Promise<IDossier> {
return this._post(dossier).toPromise();
}
getDeleted(): Promise<Dossier[]> {
return this._dossierControllerService.getDeletedDossiers().toPromise();
getDeleted(): Promise<IDossier[]> {
return this.getAll('deleted-dossiers').toPromise();
}
restore(dossierIds: Array<string>): Promise<unknown> {
return this._dossierControllerService.restoreDossiers(dossierIds).toPromise();
restore(dossierIds: List): Promise<unknown> {
return this._post(dossierIds, 'deleted-dossiers/restore').toPromise();
}
hardDelete(dossierIds: Array<string>): Promise<unknown> {
return this._dossierControllerService.hardDeleteDossiers(dossierIds).toPromise();
hardDelete(dossierIds: List): Promise<unknown> {
const body = dossierIds.map<QueryParam>(id => ({ key: 'dossierId', value: id }));
return this.delete(body, 'deleted-dossiers/hard-delete', body).toPromise();
}
private _computeStats(entities: List<Dossier>): IDossiersStats {
let totalAnalyzedPages = 0;
const totalPeople = new Set<string>();
entities.forEach(dossier => {
dossier.memberIds?.forEach(m => totalPeople.add(m));
totalAnalyzedPages += dossier.totalNumberOfPages;
});
return {
totalPeople: totalPeople.size,
totalAnalyzedPages
};
}
}

View File

@ -1,13 +1,13 @@
import { Injectable } from '@angular/core';
import { AppStateService } from '@state/app-state.service';
import { UserService } from '@services/user.service';
import { ReanalysisControllerService, StatusControllerService } from '@redaction/red-ui-http';
import { FileStatusWrapper } from '@models/file/file-status.wrapper';
import { ReanalysisControllerService } from '@redaction/red-ui-http';
import { File } from '@models/file/file';
import { PermissionsService } from '@services/permissions.service';
import { isArray } from 'rxjs/internal-compatibility';
import { DossiersDialogService } from './dossiers-dialog.service';
import { ConfirmationDialogInput } from '../../shared/dialogs/confirmation-dialog/confirmation-dialog.component';
import { ConfirmationDialogInput } from '@shared/dialogs/confirmation-dialog/confirmation-dialog.component';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { FilesService } from './files.service';
@Injectable()
export class FileActionService {
@ -15,107 +15,81 @@ export class FileActionService {
private readonly _dialogService: DossiersDialogService,
private readonly _permissionsService: PermissionsService,
private readonly _userService: UserService,
private readonly _statusControllerService: StatusControllerService,
private readonly _fileService: FilesService,
private readonly _reanalysisControllerService: ReanalysisControllerService,
private readonly _appStateService: AppStateService
) {}
reanalyseFile(fileStatusWrapper?: FileStatusWrapper) {
if (!fileStatusWrapper) {
fileStatusWrapper = this._appStateService.activeFile;
}
return this._reanalysisControllerService.reanalyzeFile(
this._appStateService.activeDossier.dossierId,
fileStatusWrapper.fileId,
true
);
reanalyseFile(file = this._appStateService.activeFile) {
return this._reanalysisControllerService.reanalyzeFile(this._appStateService.activeDossier.id, file.fileId, true);
}
toggleAnalysis(fileStatusWrapper?: FileStatusWrapper) {
if (!fileStatusWrapper) {
fileStatusWrapper = this._appStateService.activeFile;
}
return this._reanalysisControllerService.toggleAnalysis(
fileStatusWrapper.dossierId,
fileStatusWrapper.fileId,
!fileStatusWrapper.excluded
);
toggleAnalysis(file = this._appStateService.activeFile) {
return this._reanalysisControllerService.toggleAnalysis(file.dossierId, file.fileId, !file.excluded);
}
async assignToMe(fileStatus?: FileStatusWrapper | FileStatusWrapper[], callback?: Function) {
async assignToMe(files?: File[], callback?: Function) {
return new Promise<void>((resolve, reject) => {
if (!isArray(fileStatus)) {
fileStatus = [fileStatus];
}
const atLeastOneFileHasReviewer = fileStatus.reduce((acc, fs) => acc || !!fs.currentReviewer, false);
const atLeastOneFileHasReviewer = files.reduce((acc, fs) => acc || !!fs.currentReviewer, false);
if (atLeastOneFileHasReviewer) {
const data = new ConfirmationDialogInput({
title: _('confirmation-dialog.assign-file-to-me.title'),
question: _('confirmation-dialog.assign-file-to-me.question')
});
this._dialogService.openDialog('confirm', null, data, () => {
this._assignReviewerToCurrentUser(fileStatus, callback)
this._assignReviewerToCurrentUser(files, callback)
.then(() => resolve())
.catch(() => reject());
});
} else {
this._assignReviewerToCurrentUser(fileStatus, callback)
this._assignReviewerToCurrentUser(files, callback)
.then(() => resolve())
.catch(() => reject());
}
});
}
setFileUnderApproval(fileStatus: FileStatusWrapper | FileStatusWrapper[], approverId?: string) {
if (!isArray(fileStatus)) {
fileStatus = [fileStatus];
}
setFilesUnderApproval(files: File[], approverId?: string) {
if (!approverId) {
approverId = this._appStateService.activeDossier.approverIds[0];
}
return this._statusControllerService.setStatusUnderApprovalForList(
fileStatus.map(f => f.fileId),
return this._fileService.setUnderApprovalFor(
files.map(f => f.fileId),
approverId,
this._appStateService.activeDossierId
);
}
setFileApproved(fileStatus: FileStatusWrapper | FileStatusWrapper[]) {
if (!isArray(fileStatus)) {
fileStatus = [fileStatus];
}
return this._statusControllerService.setStatusApprovedForList(
fileStatus.map(f => f.fileId),
setFilesApproved(files: File[]) {
return this._fileService.setApprovedFor(
files.map(f => f.fileId),
this._appStateService.activeDossierId
);
}
setFileUnderReview(fileStatus: FileStatusWrapper | FileStatusWrapper[]) {
if (!isArray(fileStatus)) {
fileStatus = [fileStatus];
}
return this._statusControllerService.setStatusUnderReviewForList(
fileStatus.map(f => f.fileId),
setFilesUnderReview(files: File[]) {
return this._fileService.setUnderReviewFor(
files.map(f => f.fileId),
this._appStateService.activeDossierId
);
}
ocrFile(fileStatus: FileStatusWrapper | FileStatusWrapper[]) {
if (!isArray(fileStatus)) {
fileStatus = [fileStatus];
}
ocrFiles(files: File[]) {
return this._reanalysisControllerService.ocrFiles(
fileStatus.map(f => f.fileId),
files.map(f => f.fileId),
this._appStateService.activeDossierId
);
}
assignFile(mode: 'reviewer' | 'approver', $event: MouseEvent, file?: FileStatusWrapper, callback?: Function, ignoreChanged = false) {
const files = file ? [file] : [this._appStateService.activeFile];
const data = { mode, files, ignoreChanged };
assignFile(
mode: 'reviewer' | 'approver',
$event: MouseEvent,
file = this._appStateService.activeFile,
callback?: Function,
ignoreChanged = false
) {
const data = { mode, files: [file], ignoreChanged };
this._dialogService.openDialog('assignFile', $event, data, async () => {
if (callback) {
callback();
@ -123,13 +97,10 @@ export class FileActionService {
});
}
private async _assignReviewerToCurrentUser(fileStatus: FileStatusWrapper | FileStatusWrapper[], callback?: Function) {
if (!isArray(fileStatus)) {
fileStatus = [fileStatus];
}
await this._statusControllerService
.setFileReviewerForList(
fileStatus.map(f => f.fileId),
private async _assignReviewerToCurrentUser(files: File[], callback?: Function) {
await this._fileService
.setReviewerFor(
files.map(f => f.fileId),
this._appStateService.activeDossierId,
this._userService.currentUser.id
)

View File

@ -0,0 +1,79 @@
import { Injectable, Injector } from '@angular/core';
import { EntitiesService, List, RequiredParam, Validate } from '@iqser/common-ui';
import { IFile } from '@redaction/red-ui-http';
import { File } from '@models/file/file';
import { TEMPORARY_INJECTOR } from './injector';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class FilesService extends EntitiesService<File, IFile> {
constructor(protected readonly _injector: Injector) {
super(TEMPORARY_INJECTOR(_injector), 'status');
}
/**
* Gets the status for all files.
*/
get(): Observable<IFile[]>;
/**
* Gets the status for a file from a dossier.
*/
get(dossierId: string, fileId: string): Observable<IFile>;
get(dossierId?: string, fileId?: string) {
if (dossierId && fileId) {
return super._getOne([dossierId, fileId]);
}
return super.getAll();
}
getFor(dossierId: string): Observable<IFile[]>;
getFor(dossierIds: List): Observable<Record<string, IFile[]>>;
getFor(args: string | List) {
if (typeof args === 'string') {
return super.getAll(`${this._defaultModelPath}/${args}`);
}
return this._post<Record<string, IFile[]>>(args);
}
@Validate()
setUnderApprovalFor(@RequiredParam() body: List, @RequiredParam() approverId: string, @RequiredParam() dossierId: string) {
const url = `${this._defaultModelPath}/underapproval/${dossierId}/bulk`;
return this._post<unknown>(body, url, [{ key: 'approverId', value: approverId }]);
}
/**
* Assigns a reviewer for a list of files.
*/
@Validate()
setReviewerFor(@RequiredParam() filesIds: List, @RequiredParam() dossierId: string, @RequiredParam() reviewerId: string) {
return this._post<unknown>(filesIds, `${this._defaultModelPath}/${dossierId}/bulk/${reviewerId}`);
}
/**
* Sets the status APPROVED for a list of files.
*/
@Validate()
setApprovedFor(@RequiredParam() filesIds: List, @RequiredParam() dossierId: string) {
return this._post<unknown>(filesIds, `${this._defaultModelPath}/approved/${dossierId}/bulk`);
}
/**
* Sets the status UNDER_REVIEW for a list of files.
*/
@Validate()
setUnderReviewFor(@RequiredParam() filesIds: List, @RequiredParam() dossierId: string) {
return this._post<unknown>(filesIds, `${this._defaultModelPath}/underreview/${dossierId}/bulk`);
}
/**
* Gets the deleted files for a dossier.
*/
@Validate()
getDeletedFilesFor(@RequiredParam() dossierId: string): Observable<IFile[]> {
return this.getAll(`${this._defaultModelPath}/softdeleted/${dossierId}`);
}
}

View File

@ -0,0 +1,16 @@
import { Injector } from "@angular/core";
import { FilterService, SearchService } from "@iqser/common-ui";
/**
* This should be removed when refactoring is done
* @param injector
* @constructor
*/
export const TEMPORARY_INJECTOR = injector =>
Injector.create({
providers: [
{ provide: FilterService, useClass: FilterService },
{ provide: SearchService, useClass: SearchService }
],
parent: injector
});

View File

@ -1,11 +1,6 @@
import { Injectable } from '@angular/core';
import { AppStateService } from '@state/app-state.service';
import {
AddRedactionRequest,
DictionaryControllerService,
ForceRedactionRequest,
ManualRedactionControllerService
} from '@redaction/red-ui-http';
import { AddRedactionRequest, ForceRedactionRequest, ManualRedactionControllerService } from '@redaction/red-ui-http';
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { Toaster } from '@iqser/common-ui';
import { TranslateService } from '@ngx-translate/core';
@ -28,7 +23,6 @@ export class ManualAnnotationService {
private readonly _translateService: TranslateService,
private readonly _toaster: Toaster,
private readonly _manualRedactionControllerService: ManualRedactionControllerService,
private readonly _dictionaryControllerService: DictionaryControllerService,
private readonly _permissionsService: PermissionsService
) {
this.CONFIG = {

View File

@ -10,7 +10,7 @@ import {
import { FileDataModel } from '@models/file/file-data.model';
import { AppStateService } from '@state/app-state.service';
import { PermissionsService } from '@services/permissions.service';
import { FileStatusWrapper } from '@models/file/file-status.wrapper';
import { File } from '@models/file/file';
@Injectable()
export class PdfViewerDataService {
@ -59,13 +59,7 @@ export class PdfViewerDataService {
return of({ pages: [] });
}
downloadOriginalFile(fileStatus: FileStatusWrapper): Observable<any> {
return this._fileManagementControllerService.downloadOriginalFile(
fileStatus.dossierId,
fileStatus.fileId,
true,
fileStatus.cacheIdentifier,
'body'
);
downloadOriginalFile(file: File): Observable<any> {
return this._fileManagementControllerService.downloadOriginalFile(file.dossierId, file.fileId, true, file.cacheIdentifier, 'body');
}
}

View File

@ -1,7 +1,7 @@
import { FileStatus } from '@redaction/red-ui-http';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
export const fileStatusTranslations: { [key in FileStatus.StatusEnum]: string } = {
export const fileStatusTranslations: { [key in FileStatus]: string } = {
APPROVED: _('file-status.approved'),
DELETED: _('file-status.deleted'),
ERROR: _('file-status.error'),

View File

@ -22,7 +22,7 @@ export const loadCompareDocumentWrapper = async (
compareDocument,
mergedDocument,
instance,
fileStatus,
file,
setCompareViewMode: () => void,
navigateToPage: () => void,
pdfNet: any
@ -44,7 +44,7 @@ export const loadCompareDocumentWrapper = async (
setCompareViewMode();
instance.loadDocument(mergedDocumentBuffer, {
filename: fileStatus ? fileStatus.filename : 'document.pdf'
filename: file?.filename ?? 'document.pdf'
});
instance.disableElements(['compareButton']);
instance.enableElements(['closeCompareButton']);

View File

@ -1,5 +1,5 @@
import { Component, ElementRef, Input, OnChanges, ViewChild } from '@angular/core';
import { TypeValueWrapper } from '@models/file/type-value.wrapper';
import { TypeValue } from '@models/file/type-value';
@Component({
selector: 'redaction-annotation-icon',
@ -10,7 +10,7 @@ export class AnnotationIconComponent implements OnChanges {
@Input() color: string;
@Input() type: 'square' | 'rhombus' | 'circle' | 'hexagon' | 'none';
@Input() label: string;
@Input() dictType: TypeValueWrapper;
@Input() dictType: TypeValue;
@ViewChild('icon', { static: true }) icon: ElementRef;

View File

@ -1,5 +1,7 @@
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
import { UserService, UserWrapper } from '@services/user.service';
import { UserService } from '@services/user.service';
import { List } from '@redaction/red-ui-http';
import { User } from '@models/user';
@Component({
selector: 'redaction-assign-user-dropdown',
@ -8,27 +10,27 @@ import { UserService, UserWrapper } from '@services/user.service';
changeDetection: ChangeDetectionStrategy.OnPush
})
export class AssignUserDropdownComponent {
oldUser: UserWrapper | string;
@Input() options: (UserWrapper | string)[];
@Output() save = new EventEmitter<UserWrapper | string>();
@Output() cancel = new EventEmitter<never>();
private _currentUser: UserWrapper | string;
oldUser: User | string;
@Input() options: List<User | string>;
@Output() readonly save = new EventEmitter<User | string>();
@Output() readonly cancel = new EventEmitter();
private _currentUser: User | string;
constructor(private readonly _userService: UserService) {}
get value(): UserWrapper | string {
get value(): User | string {
return this._currentUser;
}
@Input()
set value(value: UserWrapper | string) {
set value(value: User | string) {
if (this.oldUser === undefined) {
this.oldUser = value;
}
this._currentUser = value;
}
getContext(user: UserWrapper | string) {
getContext(user: User | string) {
return { userId: typeof user === 'string' ? user : user?.id };
}
}

View File

@ -1,7 +1,7 @@
import { ChangeDetectionStrategy, Component, Input, OnDestroy } from '@angular/core';
import { PermissionsService } from '@services/permissions.service';
import { DossierWrapper } from '@state/model/dossier.wrapper';
import { FileStatusWrapper } from '@models/file/file-status.wrapper';
import { Dossier } from '../../../../../state/model/dossier';
import { File } from '@models/file/file';
import { FileDownloadService } from '@upload-download/services/file-download.service';
import { Toaster } from '@iqser/common-ui';
import { AutoUnsubscribe, CircleButtonType, CircleButtonTypes } from '@iqser/common-ui';
@ -17,8 +17,8 @@ export type MenuState = 'OPEN' | 'CLOSED';
changeDetection: ChangeDetectionStrategy.OnPush
})
export class FileDownloadBtnComponent extends AutoUnsubscribe implements OnDestroy {
@Input() dossier: DossierWrapper;
@Input() file: FileStatusWrapper | FileStatusWrapper[];
@Input() dossier: Dossier;
@Input() file: File | File[];
@Input() tooltipPosition: 'above' | 'below' | 'before' | 'after' = 'above';
@Input() type: CircleButtonType = CircleButtonTypes.default;
@Input() tooltipClass: string;

View File

@ -1,6 +1,6 @@
import { Component, Input, OnChanges } from '@angular/core';
import { AppStateService } from '@state/app-state.service';
import { TypeValueWrapper } from '@models/file/type-value.wrapper';
import { TypeValue } from '@models/file/type-value';
@Component({
selector: 'redaction-dictionary-annotation-icon',
@ -19,7 +19,7 @@ export class DictionaryAnnotationIconComponent implements OnChanges {
ngOnChanges(): void {
if (this.dictionaryKey) {
const typeValue: TypeValueWrapper = this._appStateService.getDictionaryTypeValue(this.dictionaryKey, this.dossierTemplateId);
const typeValue: TypeValue = this._appStateService.getDictionaryTypeValue(this.dictionaryKey, this.dossierTemplateId);
this.color = this._appStateService.getDictionaryColor(this.dictionaryKey, this.dossierTemplateId);
this.type = typeValue.hint ? 'circle' : 'square';
this.label = this.dictionaryKey[0].toUpperCase();

View File

@ -1,11 +1,11 @@
import { Component, EventEmitter, Input, OnChanges, OnInit, Output } from '@angular/core';
import { DictionaryControllerService } from '@redaction/red-ui-http';
import { AppStateService } from '@state/app-state.service';
import { Debounce, IconButtonTypes } from '@iqser/common-ui';
import { Debounce, IconButtonTypes, List } from '@iqser/common-ui';
import { Observable } from 'rxjs';
import { map, take } from 'rxjs/operators';
import { DossierWrapper } from '@state/model/dossier.wrapper';
import { Dossier } from '@state/model/dossier';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { DictionaryService } from '@shared/services/dictionary.service';
import ICodeEditor = monaco.editor.ICodeEditor;
import IDiffEditor = monaco.editor.IDiffEditor;
import IModelDeltaDecoration = monaco.editor.IModelDeltaDecoration;
@ -23,14 +23,10 @@ const SMOOTH_SCROLL = 0;
export class DictionaryManagerComponent implements OnChanges, OnInit {
readonly iconButtonTypes = IconButtonTypes;
@Input()
withFloatingActions = true;
@Input()
initialEntries: string[];
@Input()
canEdit = false;
@Output()
saveDictionary = new EventEmitter<string[]>();
@Input() withFloatingActions = true;
@Input() initialEntries: List;
@Input() canEdit = false;
@Output() readonly saveDictionary = new EventEmitter<string[]>();
currentMatch = 0;
findMatches: FindMatch[] = [];
@ -48,20 +44,15 @@ export class DictionaryManagerComponent implements OnChanges, OnInit {
private _decorations: string[] = [];
private _searchDecorations: string[] = [];
constructor(
private readonly _dictionaryControllerService: DictionaryControllerService,
private readonly _appStateService: AppStateService
) {
this.currentEntries = this.initialEntries;
}
constructor(private readonly _dictionaryService: DictionaryService, private readonly _appStateService: AppStateService) {}
private _dossier: DossierWrapper = this.selectDossier as DossierWrapper;
private _dossier: Dossier = this.selectDossier as Dossier;
get dossier() {
return this._dossier;
}
set dossier(dossier: DossierWrapper) {
set dossier(dossier: Dossier) {
this._dossier = dossier;
if (dossier === this.selectDossier) {
@ -99,6 +90,8 @@ export class DictionaryManagerComponent implements OnChanges, OnInit {
}
ngOnInit(): void {
this.currentEntries = [...this.initialEntries];
this.editorOptions = {
theme: 'vs',
language: 'text/plain',
@ -125,7 +118,7 @@ export class DictionaryManagerComponent implements OnChanges, OnInit {
}
revert() {
this.currentEntries = this.initialEntries;
this.currentEntries = [...this.initialEntries];
this.searchChanged('');
}
@ -204,10 +197,10 @@ export class DictionaryManagerComponent implements OnChanges, OnInit {
this._codeEditor.revealLineInCenter(range.startLineNumber, SMOOTH_SCROLL);
}
private _onDossierChanged({ dossierId, dossierTemplateId }: DossierWrapper): Observable<string> {
const dictionary$ = this._dictionaryControllerService.getDictionaryForType(dossierTemplateId, 'dossier_redaction', dossierId);
private _onDossierChanged({ id, dossierTemplateId }: Dossier): Observable<string> {
const dictionary$ = this._dictionaryService.getFor(dossierTemplateId, 'dossier_redaction', id);
return dictionary$.pipe(map(data => this._toString(data.entries)));
return dictionary$.pipe(map(data => this._toString([...data.entries])));
}
private _toString(entries: string[]) {

View File

@ -1,7 +1,8 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnChanges, OnDestroy } from '@angular/core';
import { UserService, UserWrapper } from '@services/user.service';
import { UserService } from '@services/user.service';
import { TranslateService } from '@ngx-translate/core';
import { AutoUnsubscribe } from '@iqser/common-ui';
import { User } from '@models/user';
@Component({
selector: 'redaction-initials-avatar',
@ -20,7 +21,7 @@ export class InitialsAvatarComponent extends AutoUnsubscribe implements OnChange
displayName: string;
initials: string;
colorClass: string;
user: UserWrapper;
user: User;
constructor(
private readonly _userService: UserService,

View File

@ -15,7 +15,9 @@
<div (click)="resetFilters()" *ngIf="showResetFilters$ | async" class="reset-filters" translate="reset-filters"></div>
</div>
<div *ngIf="showCloseButton || actionConfigs || buttonConfigs" class="actions">
<div *ngIf="showCloseButton || actionConfigs || buttonConfigs || viewModeSelection" class="actions">
<ng-container [ngTemplateOutlet]="viewModeSelection"></ng-container>
<ng-container *ngFor="let config of buttonConfigs; trackBy: trackByLabel">
<iqser-icon-button
(action)="config.action($event)"
@ -53,10 +55,10 @@
<ng-template #searchBar>
<iqser-input-with-action
[(value)]="searchService.searchValue"
*ngIf="searchPlaceholder && searchService"
[(value)]="searchService.searchValue"
[class.mr-8]="searchPosition === searchPositions.beforeFilters"
[placeholder]="searchPlaceholder"
[width]="searchWidth"
[class.mr-8]="searchPosition === searchPositions.beforeFilters"
></iqser-input-with-action>
</ng-template>

Some files were not shown because too many files have changed in this diff Show More