Merge branch 'master' into RED-1546
This commit is contained in:
commit
df2565cb08
@ -7,7 +7,7 @@
|
||||
{{ message }}
|
||||
</div>
|
||||
|
||||
<div *ngIf="actions && actions.length" class="actions-wrapper">
|
||||
<div *ngIf="actions?.length" class="actions-wrapper">
|
||||
<a (click)="callAction($event, action.action)" *ngFor="let action of actions">
|
||||
{{ action.title }}
|
||||
</a>
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Toast, ToastPackage, ToastrService } from 'ngx-toastr';
|
||||
import { ToasterOptions } from '@services/toaster.service';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-toast',
|
||||
templateUrl: './toast.component.html',
|
||||
styleUrls: ['./toast.component.scss']
|
||||
})
|
||||
@ -12,12 +12,10 @@ export class ToastComponent extends Toast {
|
||||
}
|
||||
|
||||
get actions() {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
return this.options.actions;
|
||||
return (this.options as ToasterOptions)?.actions;
|
||||
}
|
||||
|
||||
callAction($event: MouseEvent, action: Function) {
|
||||
callAction($event: MouseEvent, action: () => void) {
|
||||
$event.stopPropagation();
|
||||
if (action) {
|
||||
action();
|
||||
|
||||
@ -3,7 +3,7 @@ import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { DictionaryControllerService, TypeValue } from '@redaction/red-ui-http';
|
||||
import { Observable } from 'rxjs';
|
||||
import { NotificationService, NotificationType } from '@services/notification.service';
|
||||
import { Toaster } from '@services/toaster.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { TypeValueWrapper } from '@models/file/type-value.wrapper';
|
||||
import { humanize } from '../../../../utils/functions';
|
||||
@ -21,7 +21,7 @@ export class AddEditDictionaryDialogComponent {
|
||||
constructor(
|
||||
private readonly _dictionaryControllerService: DictionaryControllerService,
|
||||
private readonly _formBuilder: FormBuilder,
|
||||
private readonly _notificationService: NotificationService,
|
||||
private readonly _toaster: Toaster,
|
||||
private readonly _translateService: TranslateService,
|
||||
private readonly _dialogRef: MatDialogRef<AddEditDictionaryDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA)
|
||||
@ -89,20 +89,16 @@ export class AddEditDictionaryDialogComponent {
|
||||
() => this._dialogRef.close(true),
|
||||
error => {
|
||||
if (error.status === 409) {
|
||||
this._notifyError('add-edit-dictionary.error.dictionary-already-exists');
|
||||
this._toaster.error('add-edit-dictionary.error.dictionary-already-exists');
|
||||
} else if (error.status === 400) {
|
||||
this._notifyError('add-edit-dictionary.error.invalid-color-or-rank');
|
||||
this._toaster.error('add-edit-dictionary.error.invalid-color-or-rank');
|
||||
} else {
|
||||
this._notifyError('add-edit-dictionary.error.generic');
|
||||
this._toaster.error('add-edit-dictionary.error.generic');
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private _notifyError(message: string) {
|
||||
this._notificationService.showToastNotification(this._translateService.instant(message), null, NotificationType.ERROR);
|
||||
}
|
||||
|
||||
private _formToObject(): TypeValue {
|
||||
return {
|
||||
caseInsensitive: !this.dictionaryForm.get('caseSensitive').value,
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
import { Component, Inject } from '@angular/core';
|
||||
import { Component, Inject, OnDestroy } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { DossierAttributeConfig, FileAttributeConfig } from '@redaction/red-ui-http';
|
||||
import { AppStateService } from '@state/app-state.service';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { LoadingService } from '@services/loading.service';
|
||||
import { ErrorMessageService } from '@services/error-message.service';
|
||||
import { NotificationService, NotificationType } from '@services/notification.service';
|
||||
import { LoadingService } from '../../../../services/loading.service';
|
||||
import { HttpErrorResponse } from '@angular/common/http';
|
||||
import { Toaster } from '../../../../services/toaster.service';
|
||||
import { AutoUnsubscribeComponent } from '../../../shared/base/auto-unsubscribe.component';
|
||||
import { DossierAttributesService } from '@shared/services/controller-wrappers/dossier-attributes.service';
|
||||
|
||||
@Component({
|
||||
@ -14,7 +14,7 @@ import { DossierAttributesService } from '@shared/services/controller-wrappers/d
|
||||
templateUrl: './add-edit-dossier-attribute-dialog.component.html',
|
||||
styleUrls: ['./add-edit-dossier-attribute-dialog.component.scss']
|
||||
})
|
||||
export class AddEditDossierAttributeDialogComponent {
|
||||
export class AddEditDossierAttributeDialogComponent extends AutoUnsubscribeComponent implements OnDestroy {
|
||||
dossierAttributeForm: FormGroup;
|
||||
dossierAttribute: DossierAttributeConfig;
|
||||
dossierTemplateId: string;
|
||||
@ -30,12 +30,12 @@ export class AddEditDossierAttributeDialogComponent {
|
||||
private readonly _formBuilder: FormBuilder,
|
||||
private readonly _loadingService: LoadingService,
|
||||
private readonly _dossierAttributesService: DossierAttributesService,
|
||||
private readonly _errorMessageService: ErrorMessageService,
|
||||
private readonly _notificationService: NotificationService,
|
||||
private readonly _toaster: Toaster,
|
||||
public dialogRef: MatDialogRef<AddEditDossierAttributeDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA)
|
||||
public data: { dossierAttribute: DossierAttributeConfig; dossierTemplateId: string }
|
||||
) {
|
||||
super();
|
||||
this.dossierAttribute = data.dossierAttribute;
|
||||
this.dossierTemplateId = data.dossierTemplateId;
|
||||
|
||||
@ -76,13 +76,9 @@ export class AddEditDossierAttributeDialogComponent {
|
||||
() => {
|
||||
this.dialogRef.close(true);
|
||||
},
|
||||
(err: HttpErrorResponse) => {
|
||||
(error: HttpErrorResponse) => {
|
||||
this._loadingService.stop();
|
||||
this._notificationService.showToastNotification(
|
||||
this._errorMessageService.getMessage(err, 'add-edit-dossier-attribute.error.generic'),
|
||||
null,
|
||||
NotificationType.ERROR
|
||||
);
|
||||
this._toaster.error('add-edit-dossier-attribute.error.generic', { error: error });
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { Component, Inject } from '@angular/core';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { Colors, DictionaryControllerService } from '@redaction/red-ui-http';
|
||||
import { NotificationService, NotificationType } from '@services/notification.service';
|
||||
import { Toaster } from '../../../../services/toaster.service';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { DefaultColorType } from '@models/default-color-key.model';
|
||||
@ -21,7 +21,7 @@ export class EditColorDialogComponent {
|
||||
constructor(
|
||||
private readonly _formBuilder: FormBuilder,
|
||||
private readonly _dictionaryControllerService: DictionaryControllerService,
|
||||
private readonly _notificationService: NotificationService,
|
||||
private readonly _toaster: Toaster,
|
||||
private readonly _translateService: TranslateService,
|
||||
private readonly _dialogRef: MatDialogRef<EditColorDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA)
|
||||
@ -50,17 +50,10 @@ export class EditColorDialogComponent {
|
||||
try {
|
||||
await this._dictionaryControllerService.setColors(colors, this._dossierTemplateId).toPromise();
|
||||
this._dialogRef.close(true);
|
||||
this._notificationService.showToastNotification(
|
||||
this._translateService.instant('edit-color-dialog.success', {
|
||||
color: this._translateService.instant('default-colors-screen.types.' + this.colorKey)
|
||||
})
|
||||
);
|
||||
const color = this._translateService.instant(`default-colors-screen.types.${this.colorKey}`);
|
||||
this._toaster.info('edit-color-dialog.success', { params: { color: color } });
|
||||
} catch (e) {
|
||||
this._notificationService.showToastNotification(
|
||||
this._translateService.instant('edit-color-dialog.error'),
|
||||
null,
|
||||
NotificationType.ERROR
|
||||
);
|
||||
this._toaster.error('edit-color-dialog.error');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,15 +2,15 @@
|
||||
<div class="select-all-container">
|
||||
<redaction-round-checkbox
|
||||
(click)="toggleSelectAll()"
|
||||
[active]="areAllEntitiesSelected"
|
||||
[indeterminate]="(areSomeEntitiesSelected$ | async) && !areAllEntitiesSelected"
|
||||
[active]="screenStateService.areAllEntitiesSelected$ | async"
|
||||
[indeterminate]="screenStateService.notAllEntitiesSelected$ | async"
|
||||
></redaction-round-checkbox>
|
||||
</div>
|
||||
<span class="all-caps-label">
|
||||
{{ 'file-attributes-csv-import.table-header.title' | translate: { length: allEntities.length } }}
|
||||
{{ 'file-attributes-csv-import.table-header.title' | translate: { length: (screenStateService.allEntitiesLength$ | async) } }}
|
||||
</span>
|
||||
|
||||
<ng-container *ngIf="areSomeEntitiesSelected$ | async">
|
||||
<ng-container *ngIf="screenStateService.areSomeEntitiesSelected$ | async">
|
||||
<redaction-circle-button
|
||||
[matMenuTriggerFor]="readOnlyMenu"
|
||||
[tooltip]="'file-attributes-csv-import.table-header.actions.read-only' | translate"
|
||||
@ -81,7 +81,7 @@
|
||||
</div>
|
||||
|
||||
<redaction-empty-state
|
||||
*ngIf="noData"
|
||||
*ngIf="screenStateService.noData$ | async"
|
||||
[text]="'file-attributes-csv-import.no-data.title' | translate"
|
||||
icon="red:attribute"
|
||||
></redaction-empty-state>
|
||||
@ -91,7 +91,7 @@
|
||||
<div
|
||||
(mouseenter)="setHoveredColumn.emit(field.csvColumn)"
|
||||
(mouseleave)="setHoveredColumn.emit()"
|
||||
*cdkVirtualFor="let field of displayedEntities$ | async"
|
||||
*cdkVirtualFor="let field of sortedDisplayedEntities$ | async; trackBy: trackByPrimaryKey"
|
||||
class="table-item"
|
||||
>
|
||||
<div (click)="toggleEntitySelected($event, field)" class="selection-column">
|
||||
|
||||
@ -20,41 +20,39 @@ export class ActiveFieldsListingComponent extends BaseListingComponent<Field> im
|
||||
@Output() toggleFieldActive = new EventEmitter<Field>();
|
||||
|
||||
readonly typeOptions = [FileAttributeConfig.TypeEnum.TEXT, FileAttributeConfig.TypeEnum.NUMBER, FileAttributeConfig.TypeEnum.DATE];
|
||||
protected readonly _primaryKey = 'id';
|
||||
|
||||
constructor(protected readonly _injector: Injector) {
|
||||
super(_injector);
|
||||
this._screenStateService.setIdKey('csvColumn');
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
if (changes.entities) {
|
||||
this._screenStateService.setEntities(this.entities);
|
||||
this._screenStateService.setDisplayedEntities(this.entities);
|
||||
this._screenStateService.updateSelection();
|
||||
this.screenStateService.setEntities(this.entities);
|
||||
this.screenStateService.updateSelection();
|
||||
}
|
||||
}
|
||||
|
||||
deactivateSelection() {
|
||||
this.allEntities.filter(field => this.isSelected(field)).forEach(field => (field.primaryAttribute = false));
|
||||
this._screenStateService.setEntities([...this.allEntities.filter(field => !this.isSelected(field))]);
|
||||
this.screenStateService.setEntities(this.allEntities.filter(field => !this.isSelected(field)));
|
||||
this.entitiesChange.emit(this.allEntities);
|
||||
this._screenStateService.setSelectedEntitiesIds([]);
|
||||
this.screenStateService.setSelectedEntities([]);
|
||||
}
|
||||
|
||||
setAttributeForSelection(attribute: string, value: any) {
|
||||
for (const csvColumn of this._screenStateService.selectedEntitiesIds) {
|
||||
this.allEntities.find(f => f.csvColumn === csvColumn)[attribute] = value;
|
||||
for (const item of this.screenStateService.selectedEntities) {
|
||||
this.allEntities.find(f => f.csvColumn === item.csvColumn)[attribute] = value;
|
||||
}
|
||||
}
|
||||
|
||||
togglePrimary(field: Field) {
|
||||
if (field.primaryAttribute) {
|
||||
field.primaryAttribute = false;
|
||||
} else {
|
||||
for (const f of this.allEntities) {
|
||||
f.primaryAttribute = false;
|
||||
}
|
||||
field.primaryAttribute = true;
|
||||
return;
|
||||
}
|
||||
|
||||
for (const f of this.allEntities) f.primaryAttribute = false;
|
||||
field.primaryAttribute = true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -90,7 +90,7 @@
|
||||
</div>
|
||||
<div *ngIf="isSearchOpen" class="search-input-container">
|
||||
<redaction-input-with-action
|
||||
[form]="searchForm"
|
||||
[form]="searchService.searchForm"
|
||||
[placeholder]="'file-attributes-csv-import.search.placeholder' | translate"
|
||||
type="search"
|
||||
width="full"
|
||||
@ -101,7 +101,7 @@
|
||||
(click)="toggleFieldActive(field)"
|
||||
(mouseenter)="setHoveredColumn(field.csvColumn)"
|
||||
(mouseleave)="setHoveredColumn()"
|
||||
*ngFor="let field of displayedEntities$ | async"
|
||||
*ngFor="let field of sortedDisplayedEntities$ | async; trackBy: trackByPrimaryKey"
|
||||
class="csv-header-pill-wrapper"
|
||||
>
|
||||
<div [class.selected]="isActive(field)" class="csv-header-pill">
|
||||
|
||||
@ -6,7 +6,7 @@ import * as Papa from 'papaparse';
|
||||
import { FileAttributeConfig, FileAttributesConfig, FileAttributesControllerService } from '@redaction/red-ui-http';
|
||||
import { Observable } from 'rxjs';
|
||||
import { map, startWith } from 'rxjs/operators';
|
||||
import { NotificationService, NotificationType } from '@services/notification.service';
|
||||
import { Toaster } from '../../../../services/toaster.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { FilterService } from '@shared/services/filter.service';
|
||||
import { SearchService } from '@shared/services/search.service';
|
||||
@ -26,12 +26,13 @@ export interface Field {
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-file-attributes-csv-import-dialog',
|
||||
templateUrl: './file-attributes-csv-import-dialog.component.html',
|
||||
styleUrls: ['./file-attributes-csv-import-dialog.component.scss'],
|
||||
providers: [FilterService, SearchService, ScreenStateService, SortingService]
|
||||
})
|
||||
export class FileAttributesCsvImportDialogComponent extends BaseListingComponent<Field> {
|
||||
protected readonly _primaryKey = 'id';
|
||||
|
||||
csvFile: File;
|
||||
dossierTemplateId: string;
|
||||
parseResult: { data: any[]; errors: any[]; meta: any; fields: Field[] };
|
||||
@ -44,18 +45,17 @@ export class FileAttributesCsvImportDialogComponent extends BaseListingComponent
|
||||
keepPreview = false;
|
||||
columnSample = [];
|
||||
initialParseConfig: { delimiter?: string; encoding?: string } = {};
|
||||
protected readonly _searchKey = 'csvColumn';
|
||||
|
||||
constructor(
|
||||
private readonly _appStateService: AppStateService,
|
||||
private readonly _fileAttributesControllerService: FileAttributesControllerService,
|
||||
private readonly _translateService: TranslateService,
|
||||
private readonly _notificationService: NotificationService,
|
||||
private readonly _toaster: Toaster,
|
||||
private readonly _formBuilder: FormBuilder,
|
||||
public dialogRef: MatDialogRef<FileAttributesCsvImportDialogComponent>,
|
||||
readonly dialogRef: MatDialogRef<FileAttributesCsvImportDialogComponent>,
|
||||
protected readonly _injector: Injector,
|
||||
@Inject(MAT_DIALOG_DATA)
|
||||
public data: {
|
||||
readonly data: {
|
||||
csv: File;
|
||||
dossierTemplateId: string;
|
||||
existingConfiguration: FileAttributesConfig;
|
||||
@ -96,8 +96,7 @@ export class FileAttributesCsvImportDialogComponent extends BaseListingComponent
|
||||
this.parseResult.meta.fields = Object.keys(this.parseResult.data[0]);
|
||||
}
|
||||
|
||||
this._screenStateService.setEntities(this.parseResult.meta.fields.map(field => this._buildAttribute(field)));
|
||||
this._screenStateService.setDisplayedEntities(this.allEntities);
|
||||
this.screenStateService.setEntities(this.parseResult.meta.fields.map(field => this._buildAttribute(field)));
|
||||
this.activeFields = [];
|
||||
|
||||
for (const entity of this.allEntities) {
|
||||
@ -204,19 +203,9 @@ export class FileAttributesCsvImportDialogComponent extends BaseListingComponent
|
||||
|
||||
try {
|
||||
await this._fileAttributesControllerService.setFileAttributesConfig(fileAttributes, this.dossierTemplateId).toPromise();
|
||||
this._notificationService.showToastNotification(
|
||||
this._translateService.instant('file-attributes-csv-import.save.success', {
|
||||
count: this.activeFields.length
|
||||
}),
|
||||
null,
|
||||
NotificationType.SUCCESS
|
||||
);
|
||||
this._toaster.success('file-attributes-csv-import.save.success', { params: { count: this.activeFields.length } });
|
||||
} catch (e) {
|
||||
this._notificationService.showToastNotification(
|
||||
this._translateService.instant('file-attributes-csv-import.save.error'),
|
||||
null,
|
||||
NotificationType.ERROR
|
||||
);
|
||||
this._toaster.error('file-attributes-csv-import.save.error');
|
||||
}
|
||||
|
||||
this.dialogRef.close(true);
|
||||
|
||||
@ -132,5 +132,3 @@
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<redaction-full-page-loading-indicator [displayed]="!viewReady"></redaction-full-page-loading-indicator>
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Component, OnDestroy } from '@angular/core';
|
||||
import { PermissionsService } from '@services/permissions.service';
|
||||
import { FormBuilder, FormGroup } from '@angular/forms';
|
||||
import { AuditControllerService, AuditResponse, AuditSearchRequest } from '@redaction/red-ui-http';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Moment } from 'moment';
|
||||
import { applyIntervalConstraints } from '@utils/date-inputs-utils';
|
||||
import { LoadingService } from '../../../../services/loading.service';
|
||||
import { AutoUnsubscribeComponent } from '../../../shared/base/auto-unsubscribe.component';
|
||||
|
||||
const PAGE_SIZE = 50;
|
||||
|
||||
@ -13,16 +14,14 @@ const PAGE_SIZE = 50;
|
||||
templateUrl: './audit-screen.component.html',
|
||||
styleUrls: ['./audit-screen.component.scss']
|
||||
})
|
||||
export class AuditScreenComponent {
|
||||
export class AuditScreenComponent extends AutoUnsubscribeComponent implements OnDestroy {
|
||||
readonly ALL_CATEGORIES = 'all-categories';
|
||||
readonly ALL_USERS = 'audit-screen.all-users';
|
||||
|
||||
filterForm: FormGroup;
|
||||
viewReady = false;
|
||||
categories: string[] = [];
|
||||
userIds: Set<string>;
|
||||
logs: AuditResponse;
|
||||
currentPage = 1;
|
||||
|
||||
private _previousFrom: Moment;
|
||||
private _previousTo: Moment;
|
||||
@ -31,8 +30,9 @@ export class AuditScreenComponent {
|
||||
readonly permissionsService: PermissionsService,
|
||||
private readonly _formBuilder: FormBuilder,
|
||||
private readonly _auditControllerService: AuditControllerService,
|
||||
private readonly _translateService: TranslateService
|
||||
private readonly _loadingService: LoadingService
|
||||
) {
|
||||
super();
|
||||
this.filterForm = this._formBuilder.group({
|
||||
category: [this.ALL_CATEGORIES],
|
||||
userId: [this.ALL_USERS],
|
||||
@ -40,7 +40,7 @@ export class AuditScreenComponent {
|
||||
to: []
|
||||
});
|
||||
|
||||
this.filterForm.valueChanges.subscribe(value => {
|
||||
this.addSubscription = this.filterForm.valueChanges.subscribe(value => {
|
||||
if (!this._updateDateFilters(value)) {
|
||||
this._fetchData();
|
||||
}
|
||||
@ -71,7 +71,7 @@ export class AuditScreenComponent {
|
||||
}
|
||||
|
||||
private _fetchData(page?: number) {
|
||||
this.viewReady = false;
|
||||
this._loadingService.start();
|
||||
const promises = [];
|
||||
const category = this.filterForm.get('category').value;
|
||||
const userId = this.filterForm.get('userId').value;
|
||||
@ -101,7 +101,7 @@ export class AuditScreenComponent {
|
||||
for (const id of this.logs.data.map(log => log.userId).filter(uid => !!uid)) {
|
||||
this.userIds.add(id);
|
||||
}
|
||||
this.viewReady = true;
|
||||
this._loadingService.stop();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -22,14 +22,12 @@
|
||||
<div class="content-container">
|
||||
<div class="header-item">
|
||||
<span class="all-caps-label">
|
||||
{{ 'default-colors-screen.table-header.title' | translate: { length: (allEntities$ | async).length } }}
|
||||
{{ 'default-colors-screen.table-header.title' | translate: { length: screenStateService.allEntitiesLength$ | async } }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="table-header" redactionSyncWidth="table-item">
|
||||
<redaction-table-col-name
|
||||
(toggleSort)="toggleSort($event)"
|
||||
[activeSortingOption]="sortingOption"
|
||||
[label]="'default-colors-screen.table-col-names.key' | translate"
|
||||
[withSort]="true"
|
||||
column="key"
|
||||
@ -46,10 +44,7 @@
|
||||
|
||||
<cdk-virtual-scroll-viewport [itemSize]="80" redactionHasScrollbar>
|
||||
<!-- Table lines -->
|
||||
<div
|
||||
*cdkVirtualFor="let color of allEntities$ | async | sortBy: sortingOption.order:sortingOption.column"
|
||||
class="table-item"
|
||||
>
|
||||
<div *cdkVirtualFor="let color of sortedDisplayedEntities$ | async; trackBy: trackByPrimaryKey" class="table-item">
|
||||
<div>
|
||||
<div [translate]="'default-colors-screen.types.' + color.key" class="table-item-title heading"></div>
|
||||
</div>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Component, Injector, OnInit } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, Component, Injector, OnInit } from '@angular/core';
|
||||
import { AppStateService } from '@state/app-state.service';
|
||||
import { Colors, DictionaryControllerService } from '@redaction/red-ui-http';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
@ -8,13 +8,14 @@ import { LoadingService } from '@services/loading.service';
|
||||
import { FilterService } from '@shared/services/filter.service';
|
||||
import { SearchService } from '@shared/services/search.service';
|
||||
import { ScreenStateService } from '@shared/services/screen-state.service';
|
||||
import { ScreenNames, SortingService } from '@services/sorting.service';
|
||||
import { BaseListingComponent } from '@shared/base/base-listing.component';
|
||||
import { DefaultColorType } from '@models/default-color-key.model';
|
||||
import { SortingService } from '../../../../services/sorting.service';
|
||||
|
||||
@Component({
|
||||
templateUrl: './default-colors-screen.component.html',
|
||||
styleUrls: ['./default-colors-screen.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
providers: [FilterService, SearchService, ScreenStateService, SortingService]
|
||||
})
|
||||
export class DefaultColorsScreenComponent
|
||||
@ -25,6 +26,7 @@ export class DefaultColorsScreenComponent
|
||||
implements OnInit
|
||||
{
|
||||
private _colorsObj: Colors;
|
||||
protected readonly _primaryKey = 'key';
|
||||
|
||||
constructor(
|
||||
private readonly _appStateService: AppStateService,
|
||||
@ -36,7 +38,6 @@ export class DefaultColorsScreenComponent
|
||||
protected readonly _injector: Injector
|
||||
) {
|
||||
super(_injector);
|
||||
this._sortingService.setScreenName(ScreenNames.DEFAULT_COLORS);
|
||||
_appStateService.activateDossierTemplate(_activatedRoute.snapshot.params.dossierTemplateId);
|
||||
}
|
||||
|
||||
@ -53,9 +54,7 @@ export class DefaultColorsScreenComponent
|
||||
colorKey: color.key,
|
||||
dossierTemplateId: this._appStateService.activeDossierTemplateId
|
||||
},
|
||||
async () => {
|
||||
await this._loadColors();
|
||||
}
|
||||
async () => await this._loadColors()
|
||||
);
|
||||
}
|
||||
|
||||
@ -63,12 +62,11 @@ export class DefaultColorsScreenComponent
|
||||
this._loadingService.start();
|
||||
const data = await this._dictionaryControllerService.getColors(this._appStateService.activeDossierTemplateId).toPromise();
|
||||
this._colorsObj = data;
|
||||
this._screenStateService.setEntities(
|
||||
Object.keys(data).map(key => ({
|
||||
key,
|
||||
value: data[key]
|
||||
}))
|
||||
);
|
||||
const entities = Object.keys(data).map(key => ({
|
||||
key,
|
||||
value: data[key]
|
||||
}));
|
||||
this.screenStateService.setEntities(entities);
|
||||
this._loadingService.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,18 +24,18 @@
|
||||
<div class="select-all-container">
|
||||
<redaction-round-checkbox
|
||||
(click)="toggleSelectAll()"
|
||||
[active]="areAllEntitiesSelected"
|
||||
[indeterminate]="(areSomeEntitiesSelected$ | async) && !areAllEntitiesSelected"
|
||||
[active]="screenStateService.areAllEntitiesSelected$ | async"
|
||||
[indeterminate]="screenStateService.notAllEntitiesSelected$ | async"
|
||||
></redaction-round-checkbox>
|
||||
</div>
|
||||
|
||||
<span class="all-caps-label">
|
||||
{{ 'dictionary-listing.table-header.title' | translate: { length: (displayedEntities$ | async)?.length } }}
|
||||
{{ 'dictionary-listing.table-header.title' | translate: { length: (screenStateService.displayedLength$ | async) } }}
|
||||
</span>
|
||||
|
||||
<redaction-circle-button
|
||||
(action)="openDeleteDictionariesDialog($event)"
|
||||
*ngIf="(areSomeEntitiesSelected$ | async) && permissionsService.isAdmin()"
|
||||
*ngIf="canBulkDelete$(permissionsService.isAdmin()) | async"
|
||||
[tooltip]="'dictionary-listing.bulk.delete' | translate"
|
||||
icon="red:trash"
|
||||
type="dark-bg"
|
||||
@ -43,7 +43,7 @@
|
||||
|
||||
<div class="attributes-actions-container">
|
||||
<redaction-input-with-action
|
||||
[form]="searchForm"
|
||||
[form]="searchService.searchForm"
|
||||
[placeholder]="'dictionary-listing.search' | translate"
|
||||
type="search"
|
||||
></redaction-input-with-action>
|
||||
@ -59,20 +59,16 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div [class.no-data]="!allEntities.length" class="table-header" redactionSyncWidth="table-item">
|
||||
<div [class.no-data]="screenStateService.noData$ | async" class="table-header" redactionSyncWidth="table-item">
|
||||
<div class="select-oval-placeholder"></div>
|
||||
|
||||
<redaction-table-col-name
|
||||
(toggleSort)="toggleSort($event)"
|
||||
[activeSortingOption]="sortingOption"
|
||||
[label]="'dictionary-listing.table-col-names.type' | translate"
|
||||
[withSort]="true"
|
||||
column="label"
|
||||
></redaction-table-col-name>
|
||||
|
||||
<redaction-table-col-name
|
||||
(toggleSort)="toggleSort($event)"
|
||||
[activeSortingOption]="sortingOption"
|
||||
[label]="'dictionary-listing.table-col-names.order-of-importance' | translate"
|
||||
[withSort]="true"
|
||||
class="flex-center"
|
||||
@ -89,7 +85,7 @@
|
||||
|
||||
<redaction-empty-state
|
||||
(action)="openAddEditDictionaryDialog()"
|
||||
*ngIf="!allEntities.length"
|
||||
*ngIf="screenStateService.noData$ | async"
|
||||
[buttonLabel]="'dictionary-listing.no-data.action' | translate"
|
||||
[showButton]="permissionsService.isAdmin()"
|
||||
[text]="'dictionary-listing.no-data.title' | translate"
|
||||
@ -97,13 +93,13 @@
|
||||
></redaction-empty-state>
|
||||
|
||||
<redaction-empty-state
|
||||
*ngIf="allEntities.length && (displayedEntities$ | async)?.length === 0"
|
||||
*ngIf="noMatch$ | async"
|
||||
[text]="'dictionary-listing.no-match.title' | translate"
|
||||
></redaction-empty-state>
|
||||
|
||||
<cdk-virtual-scroll-viewport [itemSize]="80" redactionHasScrollbar>
|
||||
<div
|
||||
*cdkVirtualFor="let dict of displayedEntities$ | async | sortBy: sortingOption.order:sortingOption.column"
|
||||
*cdkVirtualFor="let dict of sortedDisplayedEntities$ | async; trackBy: trackByPrimaryKey"
|
||||
[routerLink]="[dict.type]"
|
||||
class="table-item pointer"
|
||||
>
|
||||
@ -141,7 +137,7 @@
|
||||
<div class="actions-container">
|
||||
<div *ngIf="permissionsService.isAdmin()" class="action-buttons">
|
||||
<redaction-circle-button
|
||||
(action)="openDeleteDictionariesDialog($event, [dict.type])"
|
||||
(action)="openDeleteDictionariesDialog($event, [dict])"
|
||||
[tooltip]="'dictionary-listing.action.delete' | translate"
|
||||
icon="red:trash"
|
||||
type="dark-bg"
|
||||
@ -162,7 +158,7 @@
|
||||
|
||||
<div class="right-container" redactionHasScrollbar>
|
||||
<redaction-simple-doughnut-chart
|
||||
*ngIf="allEntities.length"
|
||||
*ngIf="(screenStateService.noData$ | async) === false"
|
||||
[config]="chartData"
|
||||
[counterText]="'dictionary-listing.stats.charts.entries' | translate"
|
||||
[radius]="82"
|
||||
|
||||
@ -12,12 +12,12 @@ import { LoadingService } from '@services/loading.service';
|
||||
import { FilterService } from '@shared/services/filter.service';
|
||||
import { SearchService } from '@shared/services/search.service';
|
||||
import { ScreenStateService } from '@shared/services/screen-state.service';
|
||||
import { ScreenNames, SortingService } from '@services/sorting.service';
|
||||
import { SortingService } from '../../../../services/sorting.service';
|
||||
import { BaseListingComponent } from '@shared/base/base-listing.component';
|
||||
import { AdminDialogService } from '../../services/admin-dialog.service';
|
||||
|
||||
const toChartConfig = (dict: TypeValueWrapper): DoughnutChartConfig => ({
|
||||
value: dict.entries ? dict.entries.length : 0,
|
||||
value: dict.entries?.length ?? 0,
|
||||
color: dict.hexColor,
|
||||
label: dict.label,
|
||||
key: dict.type
|
||||
@ -31,6 +31,8 @@ const toChartConfig = (dict: TypeValueWrapper): DoughnutChartConfig => ({
|
||||
export class DictionaryListingScreenComponent extends BaseListingComponent<TypeValueWrapper> implements OnInit {
|
||||
chartData: DoughnutChartConfig[] = [];
|
||||
|
||||
protected readonly _primaryKey = 'label';
|
||||
|
||||
constructor(
|
||||
private readonly _dialogService: AdminDialogService,
|
||||
private readonly _dictionaryControllerService: DictionaryControllerService,
|
||||
@ -43,9 +45,6 @@ export class DictionaryListingScreenComponent extends BaseListingComponent<TypeV
|
||||
) {
|
||||
super(_injector);
|
||||
_loadingService.start();
|
||||
this._sortingService.setScreenName(ScreenNames.DOSSIER_LISTING);
|
||||
this._searchService.setSearchKey('label');
|
||||
this._screenStateService.setIdKey('type');
|
||||
_appStateService.activateDossierTemplate(_activatedRoute.snapshot.params.dossierTemplateId);
|
||||
}
|
||||
|
||||
@ -53,11 +52,16 @@ export class DictionaryListingScreenComponent extends BaseListingComponent<TypeV
|
||||
this._loadDictionaryData();
|
||||
}
|
||||
|
||||
openDeleteDictionariesDialog($event?: MouseEvent, types = this._screenStateService.selectedEntitiesIds) {
|
||||
openDeleteDictionariesDialog($event?: MouseEvent, types = this.screenStateService.selectedEntities) {
|
||||
this._dialogService.openDialog('confirm', $event, null, async () => {
|
||||
this._loadingService.start();
|
||||
await this._dictionaryControllerService.deleteTypes(types, this._appStateService.activeDossierTemplateId).toPromise();
|
||||
this._screenStateService.setSelectedEntitiesIds([]);
|
||||
await this._dictionaryControllerService
|
||||
.deleteTypes(
|
||||
types.map(t => t.type),
|
||||
this._appStateService.activeDossierTemplateId
|
||||
)
|
||||
.toPromise();
|
||||
this.screenStateService.setSelectedEntities([]);
|
||||
await this._appStateService.loadDictionaryData();
|
||||
this._loadDictionaryData(false);
|
||||
this._calculateData();
|
||||
@ -88,15 +92,13 @@ export class DictionaryListingScreenComponent extends BaseListingComponent<TypeV
|
||||
const entities = Object.values(appStateDictionaryData).filter(d => !d.virtual);
|
||||
|
||||
if (!loadEntries)
|
||||
this._screenStateService.setEntities(
|
||||
this.screenStateService.setEntities(
|
||||
entities.map(dict => {
|
||||
dict.entries = this.allEntities.find(d => d.type === dict.type)?.entries || [];
|
||||
return dict;
|
||||
})
|
||||
);
|
||||
else this._screenStateService.setEntities(entities);
|
||||
|
||||
this._screenStateService.setDisplayedEntities(this.allEntities);
|
||||
else this.screenStateService.setEntities(entities);
|
||||
|
||||
if (!loadEntries) return;
|
||||
|
||||
|
||||
@ -4,25 +4,15 @@
|
||||
<redaction-admin-side-nav type="settings"></redaction-admin-side-nav>
|
||||
|
||||
<div>
|
||||
<div class="page-header">
|
||||
<div class="breadcrumb" translate="digital-signature"></div>
|
||||
|
||||
<div class="actions">
|
||||
<redaction-circle-button
|
||||
*ngIf="permissionsService.isUser()"
|
||||
[tooltip]="'common.close' | translate"
|
||||
class="ml-6"
|
||||
icon="red:close"
|
||||
redactionNavigateLastDossiersScreen
|
||||
tooltipPosition="below"
|
||||
></redaction-circle-button>
|
||||
</div>
|
||||
</div>
|
||||
<redaction-page-header
|
||||
[pageLabel]="'digital-signature' | translate"
|
||||
[showCloseButton]="permissionsService.isUser()"
|
||||
></redaction-page-header>
|
||||
|
||||
<div class="red-content-inner">
|
||||
<div class="content-container">
|
||||
<div class="content-container-content">
|
||||
<form (keyup)="formChanged()" *ngIf="digitalSignatureForm" [formGroup]="digitalSignatureForm" autocomplete="off">
|
||||
<form *ngIf="digitalSignatureForm" [formGroup]="digitalSignatureForm" autocomplete="off">
|
||||
<input #fileInput (change)="fileChanged($event, fileInput)" class="file-upload-input" hidden type="file" />
|
||||
|
||||
<redaction-empty-state
|
||||
@ -112,5 +102,3 @@
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<redaction-full-page-loading-indicator [displayed]="!viewReady"></redaction-full-page-loading-indicator>
|
||||
|
||||
@ -1,30 +1,31 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Component, OnDestroy } from '@angular/core';
|
||||
import { DigitalSignature, DigitalSignatureControllerService } from '@redaction/red-ui-http';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { NotificationService, NotificationType } from '@services/notification.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Toaster } from '../../../../services/toaster.service';
|
||||
import { PermissionsService } from '@services/permissions.service';
|
||||
import { lastIndexOfEnd } from '@utils/functions';
|
||||
import { AutoUnsubscribeComponent } from '../../../shared/base/auto-unsubscribe.component';
|
||||
import { LoadingService } from '../../../../services/loading.service';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-digital-signature-screen',
|
||||
templateUrl: './digital-signature-screen.component.html',
|
||||
styleUrls: ['./digital-signature-screen.component.scss']
|
||||
})
|
||||
export class DigitalSignatureScreenComponent {
|
||||
export class DigitalSignatureScreenComponent extends AutoUnsubscribeComponent implements OnDestroy {
|
||||
digitalSignature: DigitalSignature;
|
||||
digitalSignatureForm: FormGroup;
|
||||
|
||||
viewReady = false;
|
||||
digitalSignatureExists = false;
|
||||
|
||||
constructor(
|
||||
private readonly _digitalSignatureControllerService: DigitalSignatureControllerService,
|
||||
private readonly _notificationService: NotificationService,
|
||||
private readonly _toaster: Toaster,
|
||||
private readonly _formBuilder: FormBuilder,
|
||||
private readonly _translateService: TranslateService,
|
||||
private readonly _loadingService: LoadingService,
|
||||
readonly permissionsService: PermissionsService
|
||||
) {
|
||||
super();
|
||||
this.loadDigitalSignatureAndInitializeForm();
|
||||
}
|
||||
|
||||
@ -43,50 +44,28 @@ export class DigitalSignatureScreenComponent {
|
||||
? this._digitalSignatureControllerService.updateDigitalSignature(digitalSignature)
|
||||
: this._digitalSignatureControllerService.saveDigitalSignature(digitalSignature);
|
||||
|
||||
observable.subscribe(
|
||||
this.addSubscription = observable.subscribe(
|
||||
() => {
|
||||
this.loadDigitalSignatureAndInitializeForm();
|
||||
this._notificationService.showToastNotification(
|
||||
this._translateService.instant('digital-signature-screen.action.save-success'),
|
||||
null,
|
||||
NotificationType.SUCCESS
|
||||
);
|
||||
this._toaster.success('digital-signature-screen.action.save-success');
|
||||
},
|
||||
error => {
|
||||
if (error.status === 400) {
|
||||
this._notificationService.showToastNotification(
|
||||
this._translateService.instant('digital-signature-screen.action.certificate-not-valid-error'),
|
||||
null,
|
||||
NotificationType.ERROR
|
||||
);
|
||||
this._toaster.error('digital-signature-screen.action.certificate-not-valid-error');
|
||||
} else {
|
||||
this._notificationService.showToastNotification(
|
||||
this._translateService.instant('digital-signature-screen.action.save-error'),
|
||||
null,
|
||||
NotificationType.ERROR
|
||||
);
|
||||
this._toaster.error('digital-signature-screen.action.save-error');
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
removeDigitalSignature() {
|
||||
this._digitalSignatureControllerService.deleteDigitalSignature().subscribe(
|
||||
this.addSubscription = this._digitalSignatureControllerService.deleteDigitalSignature().subscribe(
|
||||
() => {
|
||||
this.loadDigitalSignatureAndInitializeForm();
|
||||
this._notificationService.showToastNotification(
|
||||
this._translateService.instant('digital-signature-screen.action.delete-success'),
|
||||
null,
|
||||
NotificationType.SUCCESS
|
||||
);
|
||||
this._toaster.success('digital-signature-screen.action.delete-success');
|
||||
},
|
||||
() => {
|
||||
this._notificationService.showToastNotification(
|
||||
this._translateService.instant('digital-signature-screen.action.delete-error'),
|
||||
null,
|
||||
NotificationType.ERROR
|
||||
);
|
||||
}
|
||||
() => this._toaster.error('digital-signature-screen.action.delete-error')
|
||||
);
|
||||
}
|
||||
|
||||
@ -104,8 +83,8 @@ export class DigitalSignatureScreenComponent {
|
||||
}
|
||||
|
||||
loadDigitalSignatureAndInitializeForm() {
|
||||
this.viewReady = false;
|
||||
this._digitalSignatureControllerService
|
||||
this._loadingService.start();
|
||||
this.addSubscription = this._digitalSignatureControllerService
|
||||
.getDigitalSignature()
|
||||
.subscribe(
|
||||
digitalSignature => {
|
||||
@ -119,12 +98,10 @@ export class DigitalSignatureScreenComponent {
|
||||
)
|
||||
.add(() => {
|
||||
this._initForm();
|
||||
this.viewReady = true;
|
||||
this._loadingService.stop();
|
||||
});
|
||||
}
|
||||
|
||||
formChanged() {}
|
||||
|
||||
private _initForm() {
|
||||
this.digitalSignatureForm = this._formBuilder.group({
|
||||
certificateName: [this.digitalSignature.certificateName, Validators.required],
|
||||
|
||||
@ -20,22 +20,25 @@
|
||||
<redaction-admin-side-nav type="dossier-templates"></redaction-admin-side-nav>
|
||||
|
||||
<div class="content-container">
|
||||
<div *ngIf="(allEntities$ | async)?.length" class="header-item">
|
||||
<div *ngIf="(screenStateService.noData$ | async) === false" class="header-item">
|
||||
<div class="select-all-container">
|
||||
<redaction-round-checkbox
|
||||
(click)="toggleSelectAll()"
|
||||
[active]="areAllEntitiesSelected"
|
||||
[indeterminate]="(areSomeEntitiesSelected$ | async) && !areAllEntitiesSelected"
|
||||
[active]="screenStateService.areAllEntitiesSelected$ | async"
|
||||
[indeterminate]="screenStateService.notAllEntitiesSelected$ | async"
|
||||
></redaction-round-checkbox>
|
||||
</div>
|
||||
|
||||
<span class="all-caps-label">
|
||||
{{ 'dossier-attributes-listing.table-header.title' | translate: { length: (displayedEntities$ | async)?.length } }}
|
||||
{{
|
||||
'dossier-attributes-listing.table-header.title'
|
||||
| translate: { length: (screenStateService.displayedLength$ | async) }
|
||||
}}
|
||||
</span>
|
||||
|
||||
<redaction-circle-button
|
||||
(action)="openConfirmDeleteAttributeDialog($event)"
|
||||
*ngIf="permissionsService.isAdmin() && areSomeEntitiesSelected$ | async"
|
||||
*ngIf="permissionsService.isAdmin() && screenStateService.areSomeEntitiesSelected$ | async"
|
||||
[tooltip]="'dossier-attributes-listing.bulk.delete' | translate"
|
||||
icon="red:trash"
|
||||
type="dark-bg"
|
||||
@ -43,7 +46,7 @@
|
||||
|
||||
<div class="attributes-actions-container">
|
||||
<redaction-input-with-action
|
||||
[form]="searchForm"
|
||||
[form]="searchService.searchForm"
|
||||
[placeholder]="'dossier-attributes-listing.search' | translate"
|
||||
type="search"
|
||||
></redaction-input-with-action>
|
||||
@ -58,42 +61,46 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="(allEntities$ | async)?.length" class="table-header" redactionSyncWidth="table-item">
|
||||
<div *ngIf="(screenStateService.noData$ | async) === false" class="table-header" redactionSyncWidth="table-item">
|
||||
<div class="select-oval-placeholder"></div>
|
||||
|
||||
<redaction-table-col-name
|
||||
(toggleSort)="toggleSort($event)"
|
||||
[activeSortingOption]="sortingOption"
|
||||
[label]="'dossier-attributes-listing.table-col-names.label' | translate"
|
||||
[withSort]="true"
|
||||
column="label"
|
||||
></redaction-table-col-name>
|
||||
<redaction-table-col-name label="dossier-attributes-listing.table-col-names.placeholder"></redaction-table-col-name>
|
||||
|
||||
<redaction-table-col-name
|
||||
[label]="'dossier-attributes-listing.table-col-names.placeholder' | translate"
|
||||
></redaction-table-col-name>
|
||||
|
||||
<redaction-table-col-name
|
||||
(toggleSort)="toggleSort($event)"
|
||||
[activeSortingOption]="sortingOption"
|
||||
[label]="'dossier-attributes-listing.table-col-names.type' | translate"
|
||||
[withSort]="true"
|
||||
column="type"
|
||||
></redaction-table-col-name>
|
||||
|
||||
<div></div>
|
||||
<div class="scrollbar-placeholder"></div>
|
||||
</div>
|
||||
|
||||
<redaction-empty-state
|
||||
(action)="openAddEditAttributeDialog($event)"
|
||||
*ngIf="noData"
|
||||
*ngIf="screenStateService.noData$ | async"
|
||||
[buttonLabel]="'dossier-attributes-listing.no-data.action' | translate"
|
||||
[showButton]="permissionsService.isAdmin()"
|
||||
[text]="'dossier-attributes-listing.no-data.title' | translate"
|
||||
icon="red:attribute"
|
||||
></redaction-empty-state>
|
||||
|
||||
<redaction-empty-state *ngIf="noMatch" [text]="'dossier-attributes-listing.no-match.title' | translate"></redaction-empty-state>
|
||||
<redaction-empty-state
|
||||
*ngIf="noMatch$ | async"
|
||||
[text]="'dossier-attributes-listing.no-match.title' | translate"
|
||||
></redaction-empty-state>
|
||||
|
||||
<cdk-virtual-scroll-viewport [itemSize]="50" redactionHasScrollbar>
|
||||
<div
|
||||
*cdkVirtualFor="let attribute of displayedEntities$ | async | sortBy: sortingOption.order:sortingOption.column"
|
||||
*cdkVirtualFor="let attribute of sortedDisplayedEntities$ | async; trackBy: trackByPrimaryKey"
|
||||
class="table-item pointer"
|
||||
>
|
||||
<div (click)="toggleEntitySelected($event, attribute)" class="selection-column">
|
||||
@ -118,15 +125,14 @@
|
||||
[tooltip]="'dossier-attributes-listing.action.edit' | translate"
|
||||
icon="red:edit"
|
||||
type="dark-bg"
|
||||
>
|
||||
</redaction-circle-button>
|
||||
></redaction-circle-button>
|
||||
|
||||
<redaction-circle-button
|
||||
(action)="openConfirmDeleteAttributeDialog($event, attribute)"
|
||||
[tooltip]="'file-attributes-listing.action.delete' | translate"
|
||||
icon="red:trash"
|
||||
type="dark-bg"
|
||||
>
|
||||
</redaction-circle-button>
|
||||
></redaction-circle-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="scrollbar-placeholder"></div>
|
||||
|
||||
@ -5,7 +5,7 @@ import { AppStateService } from '@state/app-state.service';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { AdminDialogService } from '../../services/admin-dialog.service';
|
||||
import { LoadingService } from '@services/loading.service';
|
||||
import { ScreenNames, SortingService } from '@services/sorting.service';
|
||||
import { SortingService } from '../../../../services/sorting.service';
|
||||
import { FilterService } from '@shared/services/filter.service';
|
||||
import { SearchService } from '@shared/services/search.service';
|
||||
import { ScreenStateService } from '@shared/services/screen-state.service';
|
||||
@ -18,6 +18,8 @@ import { DossierAttributesService } from '@shared/services/controller-wrappers/d
|
||||
providers: [FilterService, SearchService, ScreenStateService, SortingService]
|
||||
})
|
||||
export class DossierAttributesListingScreenComponent extends BaseListingComponent<DossierAttributeConfig> implements OnInit {
|
||||
protected readonly _primaryKey = 'label';
|
||||
|
||||
constructor(
|
||||
protected readonly _injector: Injector,
|
||||
private readonly _appStateService: AppStateService,
|
||||
@ -28,9 +30,6 @@ export class DossierAttributesListingScreenComponent extends BaseListingComponen
|
||||
readonly permissionsService: PermissionsService
|
||||
) {
|
||||
super(_injector);
|
||||
this._searchService.setSearchKey('label');
|
||||
this._screenStateService.setIdKey('id');
|
||||
this._sortingService.setScreenName(ScreenNames.DOSSIER_ATTRIBUTES_LISTING);
|
||||
_appStateService.activateDossierTemplate(_activatedRoute.snapshot.params.dossierTemplateId);
|
||||
}
|
||||
|
||||
@ -41,7 +40,7 @@ export class DossierAttributesListingScreenComponent extends BaseListingComponen
|
||||
openConfirmDeleteAttributeDialog($event: MouseEvent, dossierAttribute?: DossierAttributeConfig) {
|
||||
this._dialogService.openDialog('confirm', $event, null, async () => {
|
||||
this._loadingService.start();
|
||||
const ids = dossierAttribute ? [dossierAttribute.id] : this._screenStateService.selectedEntitiesIds;
|
||||
const ids = dossierAttribute ? [dossierAttribute.id] : this.screenStateService.selectedEntities.map(item => item.id);
|
||||
await this._dossierAttributesService.deleteConfigs(ids);
|
||||
await this._loadData();
|
||||
});
|
||||
@ -61,8 +60,7 @@ export class DossierAttributesListingScreenComponent extends BaseListingComponen
|
||||
private async _loadData() {
|
||||
this._loadingService.start();
|
||||
const attributes = await this._dossierAttributesService.getConfig();
|
||||
this._screenStateService.setEntities(attributes);
|
||||
this.filterService.filterEntities();
|
||||
this.screenStateService.setEntities(attributes);
|
||||
this._loadingService.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,17 +4,10 @@
|
||||
<redaction-admin-side-nav type="settings"></redaction-admin-side-nav>
|
||||
|
||||
<div>
|
||||
<div class="page-header">
|
||||
<div class="breadcrumb" translate="dossier-templates"></div>
|
||||
|
||||
<redaction-circle-button
|
||||
*ngIf="permissionsService.isUser()"
|
||||
[tooltip]="'common.close' | translate"
|
||||
icon="red:close"
|
||||
redactionNavigateLastDossiersScreen
|
||||
tooltipPosition="below"
|
||||
></redaction-circle-button>
|
||||
</div>
|
||||
<redaction-page-header
|
||||
[pageLabel]="'dossier-templates' | translate"
|
||||
[showCloseButton]="permissionsService.isUser()"
|
||||
></redaction-page-header>
|
||||
|
||||
<div class="red-content-inner">
|
||||
<div class="content-container">
|
||||
@ -22,28 +15,29 @@
|
||||
<div class="select-all-container">
|
||||
<redaction-round-checkbox
|
||||
(click)="toggleSelectAll()"
|
||||
[active]="areAllEntitiesSelected"
|
||||
[indeterminate]="(areSomeEntitiesSelected$ | async) && !areAllEntitiesSelected"
|
||||
[active]="screenStateService.areAllEntitiesSelected$ | async"
|
||||
[indeterminate]="screenStateService.notAllEntitiesSelected$ | async"
|
||||
></redaction-round-checkbox>
|
||||
</div>
|
||||
|
||||
<span class="all-caps-label">
|
||||
{{ 'dossier-templates-listing.table-header.title' | translate: { length: (displayedEntities$ | async).length } }}
|
||||
{{
|
||||
'dossier-templates-listing.table-header.title'
|
||||
| translate: { length: (screenStateService.displayedLength$ | async) }
|
||||
}}
|
||||
</span>
|
||||
|
||||
<ng-container *ngIf="areSomeEntitiesSelected$ | async">
|
||||
<redaction-circle-button
|
||||
(action)="openDeleteTemplatesDialog($event)"
|
||||
*ngIf="permissionsService.isAdmin()"
|
||||
[tooltip]="'dossier-templates-listing.bulk.delete' | translate"
|
||||
icon="red:trash"
|
||||
type="dark-bg"
|
||||
></redaction-circle-button>
|
||||
</ng-container>
|
||||
<redaction-circle-button
|
||||
(action)="openDeleteTemplatesDialog($event)"
|
||||
*ngIf="canBulkDelete$(permissionsService.isAdmin()) | async"
|
||||
icon="red:trash"
|
||||
[tooltip]="'dossier-templates-listing.bulk.delete' | translate"
|
||||
type="dark-bg"
|
||||
></redaction-circle-button>
|
||||
|
||||
<div class="actions flex-1">
|
||||
<redaction-input-with-action
|
||||
[form]="searchForm"
|
||||
[form]="searchService.searchForm"
|
||||
[placeholder]="'dossier-templates-listing.search' | translate"
|
||||
type="search"
|
||||
></redaction-input-with-action>
|
||||
@ -58,12 +52,10 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div [class.no-data]="noData" class="table-header" redactionSyncWidth="table-item">
|
||||
<div [class.no-data]="screenStateService.noData$ | async" class="table-header" redactionSyncWidth="table-item">
|
||||
<div class="select-oval-placeholder"></div>
|
||||
|
||||
<redaction-table-col-name
|
||||
(toggleSort)="toggleSort($event)"
|
||||
[activeSortingOption]="sortingOption"
|
||||
[label]="'dossier-templates-listing.table-col-names.name' | translate"
|
||||
[withSort]="true"
|
||||
column="name"
|
||||
@ -73,38 +65,33 @@
|
||||
class="user-column"
|
||||
></redaction-table-col-name>
|
||||
<redaction-table-col-name
|
||||
(toggleSort)="toggleSort($event)"
|
||||
[activeSortingOption]="sortingOption"
|
||||
[label]="'dossier-templates-listing.table-col-names.created-on' | translate"
|
||||
[withSort]="true"
|
||||
column="dateAdded"
|
||||
></redaction-table-col-name>
|
||||
<redaction-table-col-name
|
||||
(toggleSort)="toggleSort($event)"
|
||||
[activeSortingOption]="sortingOption"
|
||||
[label]="'dossier-templates-listing.table-col-names.modified-on' | translate"
|
||||
[withSort]="true"
|
||||
column="dateModified"
|
||||
></redaction-table-col-name>
|
||||
|
||||
<div class="scrollbar-placeholder"></div>
|
||||
</div>
|
||||
|
||||
<redaction-empty-state
|
||||
*ngIf="noData"
|
||||
*ngIf="screenStateService.noData$ | async"
|
||||
[text]="'dossier-templates-listing.no-data.title' | translate"
|
||||
icon="red:template"
|
||||
></redaction-empty-state>
|
||||
|
||||
<redaction-empty-state
|
||||
*ngIf="noMatch"
|
||||
*ngIf="noMatch$ | async"
|
||||
[text]="'dossier-templates-listing.no-match.title' | translate"
|
||||
></redaction-empty-state>
|
||||
|
||||
<cdk-virtual-scroll-viewport [itemSize]="80" redactionHasScrollbar>
|
||||
<div
|
||||
*cdkVirtualFor="
|
||||
let dossierTemplate of displayedEntities$ | async | sortBy: sortingOption.order:sortingOption.column
|
||||
"
|
||||
*cdkVirtualFor="let dossierTemplate of sortedDisplayedEntities$ | async"
|
||||
[routerLink]="[dossierTemplate.dossierTemplateId, 'dictionaries']"
|
||||
class="table-item pointer"
|
||||
>
|
||||
|
||||
@ -9,8 +9,8 @@ import { DossierTemplateControllerService } from '@redaction/red-ui-http';
|
||||
import { FilterService } from '@shared/services/filter.service';
|
||||
import { SearchService } from '@shared/services/search.service';
|
||||
import { ScreenStateService } from '@shared/services/screen-state.service';
|
||||
import { ScreenNames, SortingService } from '@services/sorting.service';
|
||||
import { BaseListingComponent } from '@shared/base/base-listing.component';
|
||||
import { SortingService } from '../../../../services/sorting.service';
|
||||
|
||||
@Component({
|
||||
templateUrl: './dossier-templates-listing-screen.component.html',
|
||||
@ -19,6 +19,8 @@ import { BaseListingComponent } from '@shared/base/base-listing.component';
|
||||
providers: [FilterService, SearchService, ScreenStateService, SortingService]
|
||||
})
|
||||
export class DossierTemplatesListingScreenComponent extends BaseListingComponent<DossierTemplateModelWrapper> implements OnInit {
|
||||
protected _primaryKey = 'name';
|
||||
|
||||
constructor(
|
||||
private readonly _dialogService: AdminDialogService,
|
||||
private readonly _appStateService: AppStateService,
|
||||
@ -29,9 +31,6 @@ export class DossierTemplatesListingScreenComponent extends BaseListingComponent
|
||||
readonly userPreferenceService: UserPreferenceService
|
||||
) {
|
||||
super(_injector);
|
||||
this._sortingService.setScreenName(ScreenNames.DOSSIER_TEMPLATES_LISTING);
|
||||
this._searchService.setSearchKey('name');
|
||||
this._screenStateService.setIdKey('dossierTemplateId');
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
@ -41,8 +40,10 @@ export class DossierTemplatesListingScreenComponent extends BaseListingComponent
|
||||
openDeleteTemplatesDialog($event?: MouseEvent) {
|
||||
return this._dialogService.openDialog('confirm', $event, null, async () => {
|
||||
this._loadingService.start();
|
||||
await this._dossierTemplateControllerService.deleteDossierTemplates(this._screenStateService.selectedEntitiesIds).toPromise();
|
||||
this._screenStateService.setSelectedEntitiesIds([]);
|
||||
await this._dossierTemplateControllerService
|
||||
.deleteDossierTemplates(this.screenStateService.selectedEntities.map(d => d.dossierTemplateId))
|
||||
.toPromise();
|
||||
this.screenStateService.setSelectedEntities([]);
|
||||
await this._appStateService.loadAllDossierTemplates();
|
||||
await this._appStateService.loadDictionaryData();
|
||||
this.loadDossierTemplatesData();
|
||||
@ -52,8 +53,7 @@ export class DossierTemplatesListingScreenComponent extends BaseListingComponent
|
||||
loadDossierTemplatesData() {
|
||||
this._loadingService.start();
|
||||
this._appStateService.reset();
|
||||
this._screenStateService.setEntities(this._appStateService.dossierTemplates);
|
||||
this.filterService.filterEntities();
|
||||
this.screenStateService.setEntities(this._appStateService.dossierTemplates);
|
||||
this._loadDossierTemplateStats();
|
||||
this._loadingService.stop();
|
||||
}
|
||||
@ -67,7 +67,7 @@ export class DossierTemplatesListingScreenComponent extends BaseListingComponent
|
||||
}
|
||||
|
||||
private _loadDossierTemplateStats() {
|
||||
this._screenStateService.entities.forEach(rs => {
|
||||
this.screenStateService.allEntities.forEach(rs => {
|
||||
const dictionaries = this._appStateService.dictionaryData[rs.dossierTemplateId];
|
||||
if (dictionaries) {
|
||||
rs.dictionariesCount = Object.keys(dictionaries)
|
||||
|
||||
@ -24,27 +24,28 @@
|
||||
<div class="select-all-container">
|
||||
<redaction-round-checkbox
|
||||
(click)="toggleSelectAll()"
|
||||
[active]="areAllEntitiesSelected"
|
||||
[indeterminate]="(areSomeEntitiesSelected$ | async) && !areAllEntitiesSelected"
|
||||
[active]="screenStateService.areAllEntitiesSelected$ | async"
|
||||
[indeterminate]="screenStateService.notAllEntitiesSelected$ | async"
|
||||
></redaction-round-checkbox>
|
||||
</div>
|
||||
|
||||
<span class="all-caps-label">
|
||||
{{ 'file-attributes-listing.table-header.title' | translate: { length: (displayedEntities$ | async)?.length } }}
|
||||
{{
|
||||
'file-attributes-listing.table-header.title' | translate: { length: (screenStateService.displayedLength$ | async) }
|
||||
}}
|
||||
</span>
|
||||
|
||||
<redaction-circle-button
|
||||
(click)="openConfirmDeleteAttributeDialog($event)"
|
||||
*ngIf="permissionsService.isAdmin() && areSomeEntitiesSelected$ | async"
|
||||
*ngIf="canBulkDelete$(permissionsService.isAdmin()) | async"
|
||||
[tooltip]="'file-attributes-listing.bulk-actions.delete' | translate"
|
||||
icon="red:trash"
|
||||
type="dark-bg"
|
||||
>
|
||||
</redaction-circle-button>
|
||||
></redaction-circle-button>
|
||||
|
||||
<div class="attributes-actions-container">
|
||||
<redaction-input-with-action
|
||||
[form]="searchForm"
|
||||
[form]="searchService.searchForm"
|
||||
[placeholder]="'file-attributes-listing.search' | translate"
|
||||
type="search"
|
||||
></redaction-input-with-action>
|
||||
@ -69,28 +70,22 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div [class.no-data]="noData" class="table-header" redactionSyncWidth="table-item">
|
||||
<div [class.no-data]="screenStateService.noData$ | async" class="table-header" redactionSyncWidth="table-item">
|
||||
<div class="select-oval-placeholder"></div>
|
||||
|
||||
<redaction-table-col-name
|
||||
(toggleSort)="toggleSort($event)"
|
||||
[activeSortingOption]="sortingOption"
|
||||
[label]="'file-attributes-listing.table-col-names.name' | translate"
|
||||
[withSort]="true"
|
||||
column="label"
|
||||
></redaction-table-col-name>
|
||||
|
||||
<redaction-table-col-name
|
||||
(toggleSort)="toggleSort($event)"
|
||||
[activeSortingOption]="sortingOption"
|
||||
[label]="'file-attributes-listing.table-col-names.type' | translate"
|
||||
[withSort]="true"
|
||||
column="type"
|
||||
></redaction-table-col-name>
|
||||
|
||||
<redaction-table-col-name
|
||||
(toggleSort)="toggleSort($event)"
|
||||
[activeSortingOption]="sortingOption"
|
||||
[label]="'file-attributes-listing.table-col-names.read-only' | translate"
|
||||
[withSort]="true"
|
||||
class="flex-center"
|
||||
@ -114,19 +109,19 @@
|
||||
</div>
|
||||
|
||||
<redaction-empty-state
|
||||
*ngIf="noData"
|
||||
*ngIf="screenStateService.noData$ | async"
|
||||
[text]="'file-attributes-listing.no-data.title' | translate"
|
||||
icon="red:attribute"
|
||||
></redaction-empty-state>
|
||||
|
||||
<redaction-empty-state *ngIf="noMatch" [text]="'file-attributes-listing.no-match.title' | translate"></redaction-empty-state>
|
||||
<redaction-empty-state
|
||||
*ngIf="noMatch$ | async"
|
||||
[text]="'file-attributes-listing.no-match.title' | translate"
|
||||
></redaction-empty-state>
|
||||
|
||||
<cdk-virtual-scroll-viewport [itemSize]="80" redactionHasScrollbar>
|
||||
<!-- Table lines -->
|
||||
<div
|
||||
*cdkVirtualFor="let attribute of displayedEntities$ | async | sortBy: sortingOption.order:sortingOption.column"
|
||||
class="table-item"
|
||||
>
|
||||
<div *cdkVirtualFor="let attribute of sortedDisplayedEntities$ | async" class="table-item">
|
||||
<div (click)="toggleEntitySelected($event, attribute)" class="selection-column">
|
||||
<redaction-round-checkbox [active]="isSelected(attribute)"></redaction-round-checkbox>
|
||||
</div>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { ChangeDetectionStrategy, Component, ElementRef, Injector, OnInit, ViewChild } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, Component, ElementRef, Injector, OnDestroy, OnInit, ViewChild } from '@angular/core';
|
||||
import { PermissionsService } from '@services/permissions.service';
|
||||
import { FileAttributeConfig, FileAttributesConfig, FileAttributesControllerService } from '@redaction/red-ui-http';
|
||||
import { AppStateService } from '@state/app-state.service';
|
||||
@ -8,7 +8,7 @@ import { LoadingService } from '@services/loading.service';
|
||||
import { FilterService } from '@shared/services/filter.service';
|
||||
import { SearchService } from '@shared/services/search.service';
|
||||
import { ScreenStateService } from '@shared/services/screen-state.service';
|
||||
import { ScreenNames, SortingService } from '@services/sorting.service';
|
||||
import { SortingService } from '../../../../services/sorting.service';
|
||||
import { BaseListingComponent } from '@shared/base/base-listing.component';
|
||||
|
||||
@Component({
|
||||
@ -17,7 +17,9 @@ import { BaseListingComponent } from '@shared/base/base-listing.component';
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
providers: [FilterService, SearchService, ScreenStateService, SortingService]
|
||||
})
|
||||
export class FileAttributesListingScreenComponent extends BaseListingComponent<FileAttributeConfig> implements OnInit {
|
||||
export class FileAttributesListingScreenComponent extends BaseListingComponent<FileAttributeConfig> implements OnInit, OnDestroy {
|
||||
protected readonly _primaryKey = 'label';
|
||||
|
||||
private _existingConfiguration: FileAttributesConfig;
|
||||
@ViewChild('fileInput') private _fileInput: ElementRef;
|
||||
|
||||
@ -31,9 +33,6 @@ export class FileAttributesListingScreenComponent extends BaseListingComponent<F
|
||||
protected readonly _injector: Injector
|
||||
) {
|
||||
super(_injector);
|
||||
this._sortingService.setScreenName(ScreenNames.FILE_ATTRIBUTES_LISTING);
|
||||
this._searchService.setSearchKey('label');
|
||||
this._screenStateService.setIdKey('id');
|
||||
_appStateService.activateDossierTemplate(_activatedRoute.snapshot.params.dossierTemplateId);
|
||||
}
|
||||
|
||||
@ -65,7 +64,10 @@ export class FileAttributesListingScreenComponent extends BaseListingComponent<F
|
||||
.toPromise();
|
||||
} else {
|
||||
await this._fileAttributesService
|
||||
.deleteFileAttributes(this._screenStateService.selectedEntitiesIds, this._appStateService.activeDossierTemplateId)
|
||||
.deleteFileAttributes(
|
||||
this.screenStateService.selectedEntities.map(f => f.id),
|
||||
this._appStateService.activeDossierTemplateId
|
||||
)
|
||||
.toPromise();
|
||||
}
|
||||
await this._loadData();
|
||||
@ -89,17 +91,16 @@ export class FileAttributesListingScreenComponent extends BaseListingComponent<F
|
||||
}
|
||||
|
||||
private async _loadData() {
|
||||
this._loadingService.start();
|
||||
|
||||
try {
|
||||
this._loadingService.start();
|
||||
const response = await this._fileAttributesService
|
||||
.getFileAttributesConfiguration(this._appStateService.activeDossierTemplateId)
|
||||
.toPromise();
|
||||
this._existingConfiguration = response;
|
||||
this._screenStateService.setEntities(response?.fileAttributeConfigs || []);
|
||||
} catch (e) {
|
||||
} finally {
|
||||
this.filterService.filterEntities();
|
||||
this._loadingService.stop();
|
||||
}
|
||||
this.screenStateService.setEntities(response?.fileAttributeConfigs || []);
|
||||
} catch (e) {}
|
||||
|
||||
this._loadingService.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,22 +3,12 @@
|
||||
|
||||
<redaction-admin-side-nav type="settings"></redaction-admin-side-nav>
|
||||
|
||||
<div *ngIf="viewReady">
|
||||
<div class="page-header">
|
||||
<div class="breadcrumb" translate="license-information"></div>
|
||||
|
||||
<div class="actions">
|
||||
<button (click)="sendMail()" color="primary" mat-flat-button translate="license-info-screen.email-report"></button>
|
||||
<redaction-circle-button
|
||||
*ngIf="permissionsService.isUser()"
|
||||
[tooltip]="'common.close' | translate"
|
||||
class="ml-6"
|
||||
icon="red:close"
|
||||
redactionNavigateLastDossiersScreen
|
||||
tooltipPosition="below"
|
||||
></redaction-circle-button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<redaction-page-header
|
||||
[pageLabel]="'license-information' | translate"
|
||||
[showCloseButton]="permissionsService.isUser()"
|
||||
[buttonConfigs]="buttonConfigs"
|
||||
></redaction-page-header>
|
||||
|
||||
<div class="red-content-inner">
|
||||
<div class="content-container">
|
||||
@ -129,5 +119,3 @@
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<redaction-full-page-loading-indicator [displayed]="!viewReady"></redaction-full-page-loading-indicator>
|
||||
|
||||
@ -4,6 +4,9 @@ import { LicenseReport, LicenseReportControllerService } from '@redaction/red-ui
|
||||
import { AppConfigService } from '@app-config/app-config.service';
|
||||
import * as moment from 'moment';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { LoadingService } from '../../../../services/loading.service';
|
||||
import { ButtonConfig } from '../../../shared/components/page-header/models/button-config.model';
|
||||
import { IconButtonTypes } from '../../../shared/components/buttons/icon-button/icon-button.component';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-license-information-screen',
|
||||
@ -16,7 +19,6 @@ export class LicenseInformationScreenComponent implements OnInit {
|
||||
unlicensedInfo: LicenseReport = {};
|
||||
totalLicensedNumberOfPages = 0;
|
||||
analysisPercentageOfLicense = 100;
|
||||
viewReady = false;
|
||||
barChart: any[] = [];
|
||||
lineChartSeries: any[] = [];
|
||||
yAxisLabel = this._translateService.instant('license-info-screen.chart.pages-per-month');
|
||||
@ -31,13 +33,23 @@ export class LicenseInformationScreenComponent implements OnInit {
|
||||
group: 'Ordinal',
|
||||
domain: ['#0389ec']
|
||||
};
|
||||
buttonConfigs: ButtonConfig[] = [
|
||||
{
|
||||
label: this._translateService.instant('license-info-screen.email-report'),
|
||||
action: () => this.sendMail(),
|
||||
type: IconButtonTypes.PRIMARY
|
||||
}
|
||||
];
|
||||
|
||||
constructor(
|
||||
readonly permissionsService: PermissionsService,
|
||||
readonly appConfigService: AppConfigService,
|
||||
private readonly _licenseReportController: LicenseReportControllerService,
|
||||
private readonly _translateService: TranslateService
|
||||
) {}
|
||||
private readonly _translateService: TranslateService,
|
||||
private readonly _loadingService: LoadingService
|
||||
) {
|
||||
_loadingService.start();
|
||||
}
|
||||
|
||||
get currentYear(): number {
|
||||
return new Date().getFullYear();
|
||||
@ -68,7 +80,7 @@ export class LicenseInformationScreenComponent implements OnInit {
|
||||
|
||||
Promise.all(promises).then(reports => {
|
||||
[this.currentInfo, this.totalInfo, this.unlicensedInfo] = reports;
|
||||
this.viewReady = true;
|
||||
this._loadingService.stop();
|
||||
this.analysisPercentageOfLicense =
|
||||
this.totalLicensedNumberOfPages > 0
|
||||
? (this.currentInfo.numberOfAnalyzedPages / this.totalLicensedNumberOfPages) * 100
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { Component, ElementRef, ViewChild } from '@angular/core';
|
||||
import { Component, ElementRef, OnInit, ViewChild } from '@angular/core';
|
||||
import { PermissionsService } from '@services/permissions.service';
|
||||
import { RulesControllerService } from '@redaction/red-ui-http';
|
||||
import { NotificationService, NotificationType } from '@services/notification.service';
|
||||
import { Toaster } from '../../../../services/toaster.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { saveAs } from 'file-saver';
|
||||
import { ComponentHasChanges } from '@guards/can-deactivate.guard';
|
||||
@ -17,7 +17,7 @@ import IStandaloneEditorConstructionOptions = monaco.editor.IStandaloneEditorCon
|
||||
templateUrl: './rules-screen.component.html',
|
||||
styleUrls: ['./rules-screen.component.scss']
|
||||
})
|
||||
export class RulesScreenComponent extends ComponentHasChanges {
|
||||
export class RulesScreenComponent extends ComponentHasChanges implements OnInit {
|
||||
editorOptions: IStandaloneEditorConstructionOptions = {
|
||||
theme: 'vs',
|
||||
language: 'java',
|
||||
@ -39,13 +39,16 @@ export class RulesScreenComponent extends ComponentHasChanges {
|
||||
readonly permissionsService: PermissionsService,
|
||||
private readonly _rulesControllerService: RulesControllerService,
|
||||
private readonly _appStateService: AppStateService,
|
||||
private readonly _notificationService: NotificationService,
|
||||
private readonly _toaster: Toaster,
|
||||
protected readonly _translateService: TranslateService,
|
||||
private readonly _activatedRoute: ActivatedRoute
|
||||
) {
|
||||
super(_translateService);
|
||||
this._appStateService.activateDossierTemplate(_activatedRoute.snapshot.params.dossierTemplateId);
|
||||
this._initialize();
|
||||
_appStateService.activateDossierTemplate(_activatedRoute.snapshot.params.dossierTemplateId);
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
await this._initialize();
|
||||
}
|
||||
|
||||
get hasChanges(): boolean {
|
||||
@ -83,27 +86,20 @@ export class RulesScreenComponent extends ComponentHasChanges {
|
||||
|
||||
async save(): Promise<void> {
|
||||
this.processing = true;
|
||||
this._rulesControllerService
|
||||
await this._rulesControllerService
|
||||
.uploadRules({
|
||||
rules: this._codeEditor.getModel().getValue(),
|
||||
dossierTemplateId: this._appStateService.activeDossierTemplateId
|
||||
})
|
||||
.subscribe(
|
||||
() => {
|
||||
this._initialize();
|
||||
this._notificationService.showToastNotification(
|
||||
this._translateService.instant('rules-screen.success.generic'),
|
||||
null,
|
||||
NotificationType.SUCCESS
|
||||
);
|
||||
.toPromise()
|
||||
.then(
|
||||
async () => {
|
||||
await this._initialize();
|
||||
this._toaster.success('rules-screen.success.generic');
|
||||
},
|
||||
() => {
|
||||
this.processing = false;
|
||||
this._notificationService.showToastNotification(
|
||||
this._translateService.instant('rules-screen.error.generic'),
|
||||
null,
|
||||
NotificationType.ERROR
|
||||
);
|
||||
this._toaster.error('rules-screen.error.generic');
|
||||
}
|
||||
);
|
||||
}
|
||||
@ -148,13 +144,16 @@ export class RulesScreenComponent extends ComponentHasChanges {
|
||||
} as IModelDeltaDecoration;
|
||||
}
|
||||
|
||||
private _initialize() {
|
||||
this._rulesControllerService.downloadRules(this._appStateService.activeDossierTemplateId).subscribe(
|
||||
rules => {
|
||||
this.currentLines = this.initialLines = rules.rules.split('\n');
|
||||
this.revert();
|
||||
},
|
||||
() => (this.processing = false)
|
||||
);
|
||||
private async _initialize() {
|
||||
await this._rulesControllerService
|
||||
.downloadRules(this._appStateService.activeDossierTemplateId)
|
||||
.toPromise()
|
||||
.then(
|
||||
rules => {
|
||||
this.currentLines = this.initialLines = rules.rules.split('\n');
|
||||
this.revert();
|
||||
},
|
||||
() => (this.processing = false)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { PermissionsService } from '@services/permissions.service';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { AdminDialogService } from '../../services/admin-dialog.service';
|
||||
@ -8,15 +8,15 @@ import {
|
||||
SmtpConfigurationControllerService,
|
||||
SMTPConfigurationModel
|
||||
} from '@redaction/red-ui-http';
|
||||
import { NotificationService, NotificationType } from '@services/notification.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Toaster } from '../../../../services/toaster.service';
|
||||
import { AutoUnsubscribeComponent } from '../../../shared/base/auto-unsubscribe.component';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-smtp-config-screen',
|
||||
templateUrl: './smtp-config-screen.component.html',
|
||||
styleUrls: ['./smtp-config-screen.component.scss']
|
||||
})
|
||||
export class SmtpConfigScreenComponent implements OnInit {
|
||||
export class SmtpConfigScreenComponent extends AutoUnsubscribeComponent implements OnInit, OnDestroy {
|
||||
viewReady = false;
|
||||
configForm: FormGroup;
|
||||
generalSettings: GeneralConfigurationModel = {
|
||||
@ -31,11 +31,11 @@ export class SmtpConfigScreenComponent implements OnInit {
|
||||
private readonly _smtpConfigService: SmtpConfigurationControllerService,
|
||||
private readonly _formBuilder: FormBuilder,
|
||||
private readonly _dialogService: AdminDialogService,
|
||||
private readonly _notificationService: NotificationService,
|
||||
private readonly _translateService: TranslateService,
|
||||
private readonly _toaster: Toaster,
|
||||
private readonly _generalSettingsControllerService: GeneralSettingsControllerService
|
||||
) {
|
||||
this.configForm = this._formBuilder.group({
|
||||
super();
|
||||
this.configForm = _formBuilder.group({
|
||||
host: [undefined, Validators.required],
|
||||
port: [25],
|
||||
from: [undefined, [Validators.required, Validators.email]],
|
||||
@ -50,7 +50,7 @@ export class SmtpConfigScreenComponent implements OnInit {
|
||||
password: [undefined]
|
||||
});
|
||||
|
||||
this.configForm.controls.auth.valueChanges.subscribe(auth => {
|
||||
this.addSubscription = this.configForm.controls.auth.valueChanges.subscribe(auth => {
|
||||
if (auth) {
|
||||
this.openAuthConfigDialog();
|
||||
}
|
||||
@ -110,17 +110,9 @@ export class SmtpConfigScreenComponent implements OnInit {
|
||||
this.viewReady = false;
|
||||
try {
|
||||
await this._smtpConfigService.testSMTPConfiguration(this.configForm.getRawValue()).toPromise();
|
||||
this._notificationService.showToastNotification(
|
||||
this._translateService.instant('smtp-config-screen.test.success'),
|
||||
undefined,
|
||||
NotificationType.SUCCESS
|
||||
);
|
||||
this._toaster.success('smtp-config-screen.test.success');
|
||||
} catch (e) {
|
||||
this._notificationService.showToastNotification(
|
||||
this._translateService.instant('smtp-config-screen.test.error'),
|
||||
undefined,
|
||||
NotificationType.ERROR
|
||||
);
|
||||
this._toaster.error('smtp-config-screen.test.error');
|
||||
} finally {
|
||||
this.viewReady = true;
|
||||
}
|
||||
|
||||
@ -9,18 +9,18 @@
|
||||
<div class="select-all-container">
|
||||
<redaction-round-checkbox
|
||||
(click)="toggleSelectAll()"
|
||||
[active]="areAllEntitiesSelected"
|
||||
[indeterminate]="(areSomeEntitiesSelected$ | async) && !areAllEntitiesSelected"
|
||||
[active]="screenStateService.areAllEntitiesSelected$ | async"
|
||||
[indeterminate]="screenStateService.notAllEntitiesSelected$ | async"
|
||||
></redaction-round-checkbox>
|
||||
</div>
|
||||
|
||||
<span class="all-caps-label">
|
||||
{{ 'trash.table-header.title' | translate: { length: (displayedEntities$ | async)?.length } }}
|
||||
{{ 'trash.table-header.title' | translate: { length: (screenStateService.displayedLength$ | async) } }}
|
||||
</span>
|
||||
|
||||
<redaction-circle-button
|
||||
(action)="bulkRestore()"
|
||||
*ngIf="areSomeEntitiesSelected$ | async"
|
||||
*ngIf="screenStateService.areSomeEntitiesSelected$ | async"
|
||||
[tooltip]="'trash.bulk.restore' | translate"
|
||||
icon="red:put-back"
|
||||
type="dark-bg"
|
||||
@ -28,19 +28,17 @@
|
||||
|
||||
<redaction-circle-button
|
||||
(action)="bulkDelete()"
|
||||
*ngIf="areSomeEntitiesSelected$ | async"
|
||||
*ngIf="screenStateService.areSomeEntitiesSelected$ | async"
|
||||
[tooltip]="'trash.bulk.delete' | translate"
|
||||
icon="red:trash"
|
||||
type="dark-bg"
|
||||
></redaction-circle-button>
|
||||
</div>
|
||||
|
||||
<div [class.no-data]="noData" class="table-header" redactionSyncWidth="table-item">
|
||||
<div [class.no-data]="screenStateService.noData$ | async" class="table-header" redactionSyncWidth="table-item">
|
||||
<div class="select-oval-placeholder"></div>
|
||||
|
||||
<redaction-table-col-name
|
||||
(toggleSort)="toggleSort($event)"
|
||||
[activeSortingOption]="sortingOption"
|
||||
[label]="'trash.table-col-names.name' | translate"
|
||||
[withSort]="true"
|
||||
column="name"
|
||||
@ -50,15 +48,11 @@
|
||||
class="user-column"
|
||||
></redaction-table-col-name>
|
||||
<redaction-table-col-name
|
||||
(toggleSort)="toggleSort($event)"
|
||||
[activeSortingOption]="sortingOption"
|
||||
[label]="'trash.table-col-names.deleted-on' | translate"
|
||||
[withSort]="true"
|
||||
column="dateDeleted"
|
||||
></redaction-table-col-name>
|
||||
<redaction-table-col-name
|
||||
(toggleSort)="toggleSort($event)"
|
||||
[activeSortingOption]="sortingOption"
|
||||
[label]="'trash.table-col-names.time-to-restore' | translate"
|
||||
[withSort]="true"
|
||||
column="timeToRestore"
|
||||
@ -66,18 +60,16 @@
|
||||
<div class="scrollbar-placeholder"></div>
|
||||
</div>
|
||||
|
||||
<redaction-empty-state *ngIf="noData" [text]="'trash.no-data.title' | translate" icon="red:template"></redaction-empty-state>
|
||||
<redaction-empty-state
|
||||
*ngIf="screenStateService.noData$ | async"
|
||||
[text]="'trash.no-data.title' | translate"
|
||||
icon="red:template"
|
||||
></redaction-empty-state>
|
||||
|
||||
<redaction-empty-state *ngIf="noMatch" [text]="'trash.no-match.title' | translate"></redaction-empty-state>
|
||||
<redaction-empty-state *ngIf="noMatch$ | async" [text]="'trash.no-match.title' | translate"></redaction-empty-state>
|
||||
|
||||
<cdk-virtual-scroll-viewport [itemSize]="itemSize" redactionHasScrollbar>
|
||||
<div
|
||||
*cdkVirtualFor="
|
||||
let entity of displayedEntities$ | async | sortBy: sortingOption.order:sortingOption.column;
|
||||
trackBy: trackById
|
||||
"
|
||||
class="table-item"
|
||||
>
|
||||
<div *cdkVirtualFor="let entity of sortedDisplayedEntities$ | async; trackBy: trackByPrimaryKey" class="table-item">
|
||||
<div (click)="toggleEntitySelected($event, entity)" class="selection-column">
|
||||
<redaction-round-checkbox [active]="isSelected(entity)"></redaction-round-checkbox>
|
||||
</div>
|
||||
|
||||
@ -8,7 +8,7 @@ import * as moment from 'moment';
|
||||
import { FilterService } from '@shared/services/filter.service';
|
||||
import { SearchService } from '@shared/services/search.service';
|
||||
import { ScreenStateService } from '@shared/services/screen-state.service';
|
||||
import { ScreenNames, SortingService } from '@services/sorting.service';
|
||||
import { SortingService } from '../../../../services/sorting.service';
|
||||
import { BaseListingComponent } from '@shared/base/base-listing.component';
|
||||
import { DossiersService } from '../../../dossier/services/dossiers.service';
|
||||
|
||||
@ -21,6 +21,7 @@ import { DossiersService } from '../../../dossier/services/dossiers.service';
|
||||
export class TrashScreenComponent extends BaseListingComponent<Dossier> implements OnInit {
|
||||
readonly itemSize = 85;
|
||||
private readonly _deleteRetentionHours = this._appConfigService.getConfig(AppConfigKey.DELETE_RETENTION_HOURS);
|
||||
protected readonly _primaryKey = 'dossierName';
|
||||
|
||||
constructor(
|
||||
private readonly _appStateService: AppStateService,
|
||||
@ -32,36 +33,29 @@ export class TrashScreenComponent extends BaseListingComponent<Dossier> implemen
|
||||
private readonly _appConfigService: AppConfigService
|
||||
) {
|
||||
super(_injector);
|
||||
this._sortingService.setScreenName(ScreenNames.DOSSIER_LISTING);
|
||||
this._screenStateService.setIdKey('dossierId');
|
||||
}
|
||||
|
||||
async ngOnInit(): Promise<void> {
|
||||
this._loadingService.start();
|
||||
|
||||
await this.loadDossierTemplatesData();
|
||||
this.filterService.filterEntities();
|
||||
|
||||
this._loadingService.stop();
|
||||
}
|
||||
|
||||
async loadDossierTemplatesData(): Promise<void> {
|
||||
this._screenStateService.setEntities(await this._dossiersService.getDeletedDossiers());
|
||||
this.screenStateService.setEntities(await this._dossiersService.getDeleted());
|
||||
}
|
||||
|
||||
getRestoreDate(softDeletedTime: string): string {
|
||||
return moment(softDeletedTime).add(this._deleteRetentionHours, 'hours').toISOString();
|
||||
}
|
||||
|
||||
trackById(index: number, dossier: Dossier): string {
|
||||
return dossier.dossierId;
|
||||
}
|
||||
|
||||
bulkDelete(dossierIds = this._screenStateService.selectedEntitiesIds) {
|
||||
bulkDelete(dossierIds = this.screenStateService.selectedEntities.map(d => d.dossierId)) {
|
||||
this._loadingService.loadWhile(this._hardDelete(dossierIds));
|
||||
}
|
||||
|
||||
bulkRestore(dossierIds = this._screenStateService.selectedEntitiesIds) {
|
||||
bulkRestore(dossierIds = this.screenStateService.selectedEntities.map(d => d.dossierId)) {
|
||||
this._loadingService.loadWhile(this._restore(dossierIds));
|
||||
}
|
||||
|
||||
@ -76,9 +70,8 @@ export class TrashScreenComponent extends BaseListingComponent<Dossier> implemen
|
||||
}
|
||||
|
||||
private _removeFromList(ids: string[]): void {
|
||||
const entities = this._screenStateService.entities.filter(e => !ids.includes(e.dossierId));
|
||||
this._screenStateService.setEntities(entities);
|
||||
this._screenStateService.setSelectedEntitiesIds([]);
|
||||
this.filterService.filterEntities();
|
||||
const entities = this.screenStateService.allEntities.filter(e => !ids.includes(e.dossierId));
|
||||
this.screenStateService.setEntities(entities);
|
||||
this.screenStateService.setSelectedEntities([]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
|
||||
<div class="actions">
|
||||
<redaction-input-with-action
|
||||
[form]="searchForm"
|
||||
[form]="searchService.searchForm"
|
||||
[placeholder]="'user-listing.search' | translate"
|
||||
type="search"
|
||||
></redaction-input-with-action>
|
||||
@ -37,18 +37,18 @@
|
||||
<div class="select-all-container">
|
||||
<redaction-round-checkbox
|
||||
(click)="toggleSelectAll()"
|
||||
[active]="areAllEntitiesSelected"
|
||||
[indeterminate]="(areSomeEntitiesSelected$ | async) && !areAllEntitiesSelected"
|
||||
[active]="screenStateService.areAllEntitiesSelected$ | async"
|
||||
[indeterminate]="screenStateService.notAllEntitiesSelected$ | async"
|
||||
></redaction-round-checkbox>
|
||||
</div>
|
||||
|
||||
<span class="all-caps-label">
|
||||
{{ 'user-listing.table-header.title' | translate: { length: (displayedEntities$ | async)?.length } }}
|
||||
{{ 'user-listing.table-header.title' | translate: { length: (screenStateService.displayedLength$ | async) } }}
|
||||
</span>
|
||||
|
||||
<redaction-circle-button
|
||||
(action)="bulkDelete()"
|
||||
*ngIf="areSomeEntitiesSelected$ | async"
|
||||
*ngIf="screenStateService.areSomeEntitiesSelected$ | async"
|
||||
[disabled]="(canDeleteSelected$ | async) === false"
|
||||
[tooltip]="
|
||||
(canDeleteSelected$ | async)
|
||||
@ -79,14 +79,11 @@
|
||||
<div class="scrollbar-placeholder"></div>
|
||||
</div>
|
||||
|
||||
<redaction-empty-state
|
||||
*ngIf="(displayedEntities$ | async)?.length === 0"
|
||||
[text]="'user-listing.no-match.title' | translate"
|
||||
></redaction-empty-state>
|
||||
<redaction-empty-state *ngIf="noMatch$ | async" [text]="'user-listing.no-match.title' | translate"></redaction-empty-state>
|
||||
|
||||
<cdk-virtual-scroll-viewport [itemSize]="80" redactionHasScrollbar>
|
||||
<!-- Table lines -->
|
||||
<div *cdkVirtualFor="let user of displayedEntities$ | async; trackBy: trackById" class="table-item">
|
||||
<div *cdkVirtualFor="let user of sortedDisplayedEntities$ | async; trackBy: trackByPrimaryKey" class="table-item">
|
||||
<div (click)="toggleEntitySelected($event, user)" class="selection-column">
|
||||
<redaction-round-checkbox [active]="isSelected(user)"></redaction-round-checkbox>
|
||||
</div>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { ChangeDetectionStrategy, Component, Injector, OnInit, QueryList, ViewChildren } from '@angular/core';
|
||||
import { Component, Injector, OnInit, QueryList, ViewChildren } from '@angular/core';
|
||||
import { PermissionsService } from '@services/permissions.service';
|
||||
import { UserService } from '@services/user.service';
|
||||
import { User, UserControllerService } from '@redaction/red-ui-http';
|
||||
@ -19,10 +19,12 @@ import { map } from 'rxjs/operators';
|
||||
@Component({
|
||||
templateUrl: './user-listing-screen.component.html',
|
||||
styleUrls: ['./user-listing-screen.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
providers: [FilterService, SearchService, ScreenStateService, SortingService]
|
||||
})
|
||||
export class UserListingScreenComponent extends BaseListingComponent<User> implements OnInit {
|
||||
protected readonly _primaryKey = 'userId';
|
||||
readonly canDeleteSelected$ = this._canDeleteSelected$;
|
||||
|
||||
collapsedDetails = false;
|
||||
chartData: DoughnutChartConfig[] = [];
|
||||
@ViewChildren(InitialsAvatarComponent)
|
||||
@ -39,12 +41,11 @@ export class UserListingScreenComponent extends BaseListingComponent<User> imple
|
||||
protected readonly _injector: Injector
|
||||
) {
|
||||
super(_injector);
|
||||
this._screenStateService.setIdKey('userId');
|
||||
}
|
||||
|
||||
get canDeleteSelected$(): Observable<boolean> {
|
||||
const entities$ = this._screenStateService.selectedEntitiesIds$;
|
||||
return entities$.pipe(map(all => all.indexOf(this.userService.userId) === -1));
|
||||
get _canDeleteSelected$(): Observable<boolean> {
|
||||
const entities$ = this.screenStateService.selectedEntities$;
|
||||
return entities$.pipe(map(all => all.indexOf(this.userService.user) === -1));
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
@ -79,17 +80,12 @@ export class UserListingScreenComponent extends BaseListingComponent<User> imple
|
||||
}
|
||||
|
||||
bulkDelete() {
|
||||
this.openDeleteUsersDialog(this._screenStateService.entities.filter(u => this.isSelected(u)));
|
||||
}
|
||||
|
||||
trackById(index: number, user: User) {
|
||||
return user.userId;
|
||||
this.openDeleteUsersDialog(this.screenStateService.allEntities.filter(u => this.isSelected(u)));
|
||||
}
|
||||
|
||||
private async _loadData() {
|
||||
this._screenStateService.setEntities(await this._userControllerService.getAllUsers().toPromise());
|
||||
this.screenStateService.setEntities(await this._userControllerService.getAllUsers().toPromise());
|
||||
await this.userService.loadAllUsers();
|
||||
this.filterService.filterEntities();
|
||||
this._computeStats();
|
||||
this._loadingService.stop();
|
||||
}
|
||||
|
||||
@ -7,8 +7,7 @@ import { HttpClient } from '@angular/common/http';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { debounce } from '@utils/debounce';
|
||||
import { WatermarkControllerService, WatermarkModelRes } from '@redaction/red-ui-http';
|
||||
import { NotificationService, NotificationType } from '@services/notification.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Toaster } from '../../../../services/toaster.service';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { BASE_HREF } from '../../../../tokens';
|
||||
import { stampPDFPage } from '../../../../utils/page-stamper';
|
||||
@ -39,15 +38,14 @@ export class WatermarkScreenComponent implements OnInit {
|
||||
readonly permissionsService: PermissionsService,
|
||||
readonly appStateService: AppStateService,
|
||||
@Inject(BASE_HREF) private readonly _baseHref: string,
|
||||
private readonly _translateService: TranslateService,
|
||||
private readonly _watermarkControllerService: WatermarkControllerService,
|
||||
private readonly _notificationService: NotificationService,
|
||||
private readonly _toaster: Toaster,
|
||||
private readonly _http: HttpClient,
|
||||
private readonly _changeDetectorRef: ChangeDetectorRef,
|
||||
private readonly _formBuilder: FormBuilder,
|
||||
private readonly _activatedRoute: ActivatedRoute
|
||||
) {
|
||||
this.appStateService.activateDossierTemplate(_activatedRoute.snapshot.params.dossierTemplateId);
|
||||
appStateService.activateDossierTemplate(_activatedRoute.snapshot.params.dossierTemplateId);
|
||||
this._initForm();
|
||||
}
|
||||
|
||||
@ -81,24 +79,12 @@ export class WatermarkScreenComponent implements OnInit {
|
||||
? this._watermarkControllerService.saveWatermark(watermark, this.appStateService.activeDossierTemplateId)
|
||||
: this._watermarkControllerService.deleteWatermark(this.appStateService.activeDossierTemplateId);
|
||||
|
||||
observable.subscribe(
|
||||
observable.toPromise().then(
|
||||
() => {
|
||||
this._loadWatermark();
|
||||
this._notificationService.showToastNotification(
|
||||
this._translateService.instant(
|
||||
watermark.text ? 'watermark-screen.action.change-success' : 'watermark-screen.action.delete-success'
|
||||
),
|
||||
null,
|
||||
NotificationType.SUCCESS
|
||||
);
|
||||
this._toaster.success(watermark.text ? 'watermark-screen.action.change-success' : 'watermark-screen.action.delete-success');
|
||||
},
|
||||
() => {
|
||||
this._notificationService.showToastNotification(
|
||||
this._translateService.instant('watermark-screen.action.error'),
|
||||
null,
|
||||
NotificationType.ERROR
|
||||
);
|
||||
}
|
||||
() => this._toaster.error('watermark-screen.action.error')
|
||||
);
|
||||
}
|
||||
|
||||
@ -188,11 +174,41 @@ export class WatermarkScreenComponent implements OnInit {
|
||||
private _initForm() {
|
||||
this.configForm = this._formBuilder.group({
|
||||
text: [{ value: null, disabled: !this.permissionsService.isAdmin() }],
|
||||
hexColor: [{ value: null, disabled: !this.permissionsService.isAdmin() }, Validators.required],
|
||||
opacity: [{ value: null, disabled: !this.permissionsService.isAdmin() }, Validators.required],
|
||||
fontSize: [{ value: null, disabled: !this.permissionsService.isAdmin() }, Validators.required],
|
||||
fontType: [{ value: null, disabled: !this.permissionsService.isAdmin() }, Validators.required],
|
||||
orientation: [{ value: null, disabled: !this.permissionsService.isAdmin() }, Validators.required]
|
||||
hexColor: [
|
||||
{
|
||||
value: null,
|
||||
disabled: !this.permissionsService.isAdmin()
|
||||
},
|
||||
Validators.required
|
||||
],
|
||||
opacity: [
|
||||
{
|
||||
value: null,
|
||||
disabled: !this.permissionsService.isAdmin()
|
||||
},
|
||||
Validators.required
|
||||
],
|
||||
fontSize: [
|
||||
{
|
||||
value: null,
|
||||
disabled: !this.permissionsService.isAdmin()
|
||||
},
|
||||
Validators.required
|
||||
],
|
||||
fontType: [
|
||||
{
|
||||
value: null,
|
||||
disabled: !this.permissionsService.isAdmin()
|
||||
},
|
||||
Validators.required
|
||||
],
|
||||
orientation: [
|
||||
{
|
||||
value: null,
|
||||
disabled: !this.permissionsService.isAdmin()
|
||||
},
|
||||
Validators.required
|
||||
]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -21,8 +21,7 @@
|
||||
[tooltip]="'dossier-overview.assign-me' | translate"
|
||||
icon="red:assign-me"
|
||||
type="dark-bg"
|
||||
>
|
||||
</redaction-circle-button>
|
||||
></redaction-circle-button>
|
||||
|
||||
<redaction-circle-button
|
||||
(action)="setToUnderApproval()"
|
||||
@ -30,8 +29,7 @@
|
||||
[tooltip]="'dossier-overview.under-approval' | translate"
|
||||
icon="red:ready-for-approval"
|
||||
type="dark-bg"
|
||||
>
|
||||
</redaction-circle-button>
|
||||
></redaction-circle-button>
|
||||
|
||||
<redaction-circle-button
|
||||
(action)="setToUnderReview()"
|
||||
@ -39,8 +37,7 @@
|
||||
[tooltip]="'dossier-overview.under-review' | translate"
|
||||
icon="red:undo"
|
||||
type="dark-bg"
|
||||
>
|
||||
</redaction-circle-button>
|
||||
></redaction-circle-button>
|
||||
|
||||
<redaction-file-download-btn [dossier]="dossier" [file]="selectedFiles"></redaction-file-download-btn>
|
||||
|
||||
@ -52,8 +49,7 @@
|
||||
[tooltip]="canApprove ? ('dossier-overview.approve' | translate) : ('dossier-overview.approve-disabled' | translate)"
|
||||
icon="red:approved"
|
||||
type="dark-bg"
|
||||
>
|
||||
</redaction-circle-button>
|
||||
></redaction-circle-button>
|
||||
|
||||
<!-- Back to approval -->
|
||||
<redaction-circle-button
|
||||
@ -62,8 +58,7 @@
|
||||
[tooltip]="'dossier-overview.under-approval' | translate"
|
||||
icon="red:undo"
|
||||
type="dark-bg"
|
||||
>
|
||||
</redaction-circle-button>
|
||||
></redaction-circle-button>
|
||||
|
||||
<redaction-circle-button
|
||||
(action)="ocr()"
|
||||
|
||||
@ -1,15 +1,14 @@
|
||||
import { ChangeDetectorRef, Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
import { Component, EventEmitter, Output } from '@angular/core';
|
||||
import { AppStateService } from '@state/app-state.service';
|
||||
import { UserService } from '@services/user.service';
|
||||
import { FileManagementControllerService, ReanalysisControllerService } from '@redaction/red-ui-http';
|
||||
import { PermissionsService } from '@services/permissions.service';
|
||||
import { FileStatusWrapper } from '@models/file/file-status.wrapper';
|
||||
import { FileActionService } from '../../services/file-action.service';
|
||||
import { from, Observable } from 'rxjs';
|
||||
import { StatusOverlayService } from '@upload-download/services/status-overlay.service';
|
||||
import { DossiersDialogService } from '../../services/dossiers-dialog.service';
|
||||
import { LoadingService } from '@services/loading.service';
|
||||
import { ConfirmationDialogInput } from '@shared/dialogs/confirmation-dialog/confirmation-dialog.component';
|
||||
import { LoadingService } from '../../../../services/loading.service';
|
||||
import { ConfirmationDialogInput } from '../../../shared/dialogs/confirmation-dialog/confirmation-dialog.component';
|
||||
import { ScreenStateService } from '../../../shared/services/screen-state.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
|
||||
@ -19,23 +18,19 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
styleUrls: ['./dossier-overview-bulk-actions.component.scss']
|
||||
})
|
||||
export class DossierOverviewBulkActionsComponent {
|
||||
@Input()
|
||||
selectedFileIds: string[];
|
||||
@Output()
|
||||
reload = new EventEmitter();
|
||||
|
||||
constructor(
|
||||
private readonly _appStateService: AppStateService,
|
||||
private readonly _userService: UserService,
|
||||
private readonly _dialogService: DossiersDialogService,
|
||||
private readonly _fileManagementControllerService: FileManagementControllerService,
|
||||
private readonly _reanalysisControllerService: ReanalysisControllerService,
|
||||
private readonly _permissionsService: PermissionsService,
|
||||
private readonly _fileActionService: FileActionService,
|
||||
private readonly _statusOverlayService: StatusOverlayService,
|
||||
private readonly _changeDetectorRef: ChangeDetectorRef,
|
||||
private readonly _loadingService: LoadingService,
|
||||
private readonly _translateService: TranslateService
|
||||
private readonly _translateService: TranslateService,
|
||||
private readonly _screenStateService: ScreenStateService<FileStatusWrapper>
|
||||
) {}
|
||||
|
||||
get dossier() {
|
||||
@ -43,30 +38,27 @@ export class DossierOverviewBulkActionsComponent {
|
||||
}
|
||||
|
||||
get selectedFiles(): FileStatusWrapper[] {
|
||||
return this.selectedFileIds.map(fileId =>
|
||||
this._appStateService.getFileById(this._appStateService.activeDossier.dossier.dossierId, fileId)
|
||||
);
|
||||
return this._screenStateService.selectedEntities;
|
||||
}
|
||||
|
||||
get areAllFilesSelected() {
|
||||
return (
|
||||
this._appStateService.activeDossier.files.length !== 0 &&
|
||||
this.selectedFileIds.length === this._appStateService.activeDossier.files.length
|
||||
this.selectedFiles.length === this._appStateService.activeDossier.files.length
|
||||
);
|
||||
}
|
||||
|
||||
get areSomeFilesSelected() {
|
||||
return this.selectedFileIds.length > 0;
|
||||
return this.selectedFiles.length > 0;
|
||||
}
|
||||
|
||||
get allSelectedFilesCanBeAssignedIntoSameState() {
|
||||
if (this.areSomeFilesSelected) {
|
||||
const selectedFiles = this.selectedFiles;
|
||||
const allFilesAreUnderReviewOrUnassigned = selectedFiles.reduce(
|
||||
const allFilesAreUnderReviewOrUnassigned = this.selectedFiles.reduce(
|
||||
(acc, file) => acc && (file.isUnderReview || file.isUnassigned),
|
||||
true
|
||||
);
|
||||
const allFilesAreUnderApproval = selectedFiles.reduce((acc, file) => acc && file.isUnderApproval, true);
|
||||
const allFilesAreUnderApproval = this.selectedFiles.reduce((acc, file) => acc && file.isUnderApproval, true);
|
||||
return allFilesAreUnderReviewOrUnassigned || allFilesAreUnderApproval;
|
||||
}
|
||||
return false;
|
||||
@ -144,11 +136,14 @@ export class DossierOverviewBulkActionsComponent {
|
||||
async () => {
|
||||
this._loadingService.start();
|
||||
await this._fileManagementControllerService
|
||||
.deleteFiles(this.selectedFileIds, this._appStateService.activeDossierId)
|
||||
.deleteFiles(
|
||||
this.selectedFiles.map(item => item.fileId),
|
||||
this._appStateService.activeDossierId
|
||||
)
|
||||
.toPromise();
|
||||
await this._appStateService.reloadActiveDossierFiles();
|
||||
this.reload.emit();
|
||||
this.selectedFileIds.splice(0, this.selectedFileIds.length);
|
||||
this._screenStateService.setSelectedEntities([]);
|
||||
this._loadingService.stop();
|
||||
}
|
||||
);
|
||||
@ -158,12 +153,9 @@ export class DossierOverviewBulkActionsComponent {
|
||||
// If more than 1 approver - show dialog and ask who to assign
|
||||
if (this._appStateService.activeDossier.approverIds.length > 1) {
|
||||
this._loadingService.start();
|
||||
const files = this.selectedFileIds.map(fileId =>
|
||||
this._appStateService.getFileById(this._appStateService.activeDossierId, fileId)
|
||||
);
|
||||
|
||||
this._dialogService.openAssignFileToUserDialog(
|
||||
files,
|
||||
this.selectedFiles,
|
||||
'approver',
|
||||
() => {
|
||||
this.reload.emit();
|
||||
@ -203,11 +195,10 @@ export class DossierOverviewBulkActionsComponent {
|
||||
|
||||
assign() {
|
||||
this._loadingService.start();
|
||||
const files = this.selectedFileIds.map(fileId => this._appStateService.getFileById(this._appStateService.activeDossierId, fileId));
|
||||
|
||||
const mode = files[0].isUnderApproval ? 'approver' : 'reviewer';
|
||||
const mode = this.selectedFiles[0].isUnderApproval ? 'approver' : 'reviewer';
|
||||
|
||||
this._dialogService.openAssignFileToUserDialog(files, mode, () => {
|
||||
this._dialogService.openAssignFileToUserDialog(this.selectedFiles, mode, () => {
|
||||
this.reload.emit();
|
||||
this._loadingService.stop();
|
||||
});
|
||||
|
||||
@ -42,9 +42,7 @@
|
||||
|
||||
<div *ngIf="hasFiles" class="mt-24">
|
||||
<redaction-simple-doughnut-chart
|
||||
(toggleFilter)="filterService.filterEntities()"
|
||||
[config]="documentsChartData"
|
||||
[filter]="filterService.getFilter$('statusFilters') | async"
|
||||
[radius]="63"
|
||||
[strokeWidth]="15"
|
||||
[subtitle]="'dossier-overview.dossier-details.charts.documents-in-dossier'"
|
||||
@ -55,8 +53,8 @@
|
||||
<div *ngIf="hasFiles" class="mt-24 legend pb-32">
|
||||
<div
|
||||
(click)="filterService.toggleFilter('needsWorkFilters', filter.key)"
|
||||
*ngFor="let filter of filterService.getFilter$('needsWorkFilters') | async"
|
||||
[class.active]="filterService.filterChecked$('needsWorkFilters', filter.key) | async"
|
||||
*ngFor="let filter of needsWorkFilters$ | async"
|
||||
[class.active]="filter.checked"
|
||||
>
|
||||
<redaction-type-filter [filter]="filter"></redaction-type-filter>
|
||||
</div>
|
||||
@ -69,9 +67,9 @@
|
||||
></redaction-dossier-details-stats>
|
||||
</div>
|
||||
|
||||
<div *ngIf="!!appStateService.activeDossier.dossier.description" class="pb-32">
|
||||
<div *ngIf="appStateService.activeDossier.dossier.description as description" class="pb-32">
|
||||
<div class="heading" translate="dossier-overview.dossier-details.description"></div>
|
||||
<div class="mt-8">{{ appStateService.activeDossier.dossier.description }}</div>
|
||||
<div class="mt-8">{{ description }}</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
|
||||
@ -7,9 +7,8 @@ import { TranslateChartService } from '@services/translate-chart.service';
|
||||
import { StatusSorter } from '@utils/sorters/status-sorter';
|
||||
import { UserService } from '@services/user.service';
|
||||
import { User } from '@redaction/red-ui-http';
|
||||
import { NotificationService } from '@services/notification.service';
|
||||
import { FilterService } from '@shared/services/filter.service';
|
||||
import { FileStatusWrapper } from '@models/file/file-status.wrapper';
|
||||
import { Toaster } from '../../../../services/toaster.service';
|
||||
import { FilterService } from '../../../shared/services/filter.service';
|
||||
import { DossierAttributeWithValue } from '@models/dossier-attributes.model';
|
||||
|
||||
@Component({
|
||||
@ -26,14 +25,16 @@ export class DossierDetailsComponent implements OnInit {
|
||||
@Output() openDossierDictionaryDialog = new EventEmitter();
|
||||
@Output() toggleCollapse = new EventEmitter();
|
||||
|
||||
readonly needsWorkFilters$ = this.filterService.getFilterModels$('needsWorkFilters');
|
||||
|
||||
constructor(
|
||||
readonly appStateService: AppStateService,
|
||||
readonly translateChartService: TranslateChartService,
|
||||
readonly permissionsService: PermissionsService,
|
||||
readonly filterService: FilterService<FileStatusWrapper>,
|
||||
readonly filterService: FilterService,
|
||||
private readonly _changeDetectorRef: ChangeDetectorRef,
|
||||
private readonly _userService: UserService,
|
||||
private readonly _notificationService: NotificationService
|
||||
private readonly _toaster: Toaster
|
||||
) {}
|
||||
|
||||
get memberIds(): string[] {
|
||||
@ -71,7 +72,7 @@ export class DossierDetailsComponent implements OnInit {
|
||||
key: key
|
||||
});
|
||||
}
|
||||
this.documentsChartData.sort((a, b) => StatusSorter[a.key] - StatusSorter[b.key]);
|
||||
this.documentsChartData.sort(StatusSorter.byStatus);
|
||||
this.documentsChartData = this.translateChartService.translateStatus(this.documentsChartData);
|
||||
this._changeDetectorRef.detectChanges();
|
||||
}
|
||||
@ -80,11 +81,11 @@ export class DossierDetailsComponent implements OnInit {
|
||||
this.owner = typeof user === 'string' ? this._userService.getRedUserById(user) : user;
|
||||
const dw = Object.assign({}, this.appStateService.activeDossier);
|
||||
dw.dossier.ownerId = this.owner.userId;
|
||||
await this.appStateService.addOrUpdateDossier(dw.dossier);
|
||||
await this.appStateService.createOrUpdateDossier(dw.dossier);
|
||||
|
||||
const ownerName = this._userService.getNameForId(this.owner.userId);
|
||||
const dossierName = this.appStateService.activeDossier.name;
|
||||
const msg = 'Successfully assigned ' + ownerName + ' to dossier: ' + dossierName;
|
||||
this._notificationService.showToastNotification(msg);
|
||||
this._toaster.info(msg);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,7 +2,6 @@ import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
import { PermissionsService } from '@services/permissions.service';
|
||||
import { DossierWrapper } from '@state/model/dossier.wrapper';
|
||||
import { StatusSorter } from '@utils/sorters/status-sorter';
|
||||
import { FileManagementControllerService } from '@redaction/red-ui-http';
|
||||
import { AppStateService } from '@state/app-state.service';
|
||||
import { DossiersDialogService } from '../../services/dossiers-dialog.service';
|
||||
|
||||
@ -19,16 +18,13 @@ export class DossierListingActionsComponent {
|
||||
constructor(
|
||||
readonly permissionsService: PermissionsService,
|
||||
readonly appStateService: AppStateService,
|
||||
private readonly _dialogService: DossiersDialogService,
|
||||
private readonly _fileManagementControllerService: FileManagementControllerService
|
||||
private readonly _dialogService: DossiersDialogService
|
||||
) {}
|
||||
|
||||
openEditDossierDialog($event: MouseEvent, dossierWrapper: DossierWrapper) {
|
||||
this._dialogService.openDialog('editDossier', $event, {
|
||||
dossierWrapper,
|
||||
afterSave: () => {
|
||||
this.actionPerformed.emit();
|
||||
}
|
||||
afterSave: () => this.actionPerformed.emit()
|
||||
});
|
||||
}
|
||||
|
||||
@ -51,7 +47,7 @@ export class DossierListingActionsComponent {
|
||||
}, {});
|
||||
|
||||
return Object.keys(obj)
|
||||
.sort((a, b) => StatusSorter[a] - StatusSorter[b])
|
||||
.sort(StatusSorter.byStatus)
|
||||
.map(status => ({ length: obj[status], color: status }));
|
||||
}
|
||||
}
|
||||
|
||||
@ -27,7 +27,6 @@
|
||||
<div>
|
||||
<redaction-simple-doughnut-chart
|
||||
[config]="documentsChartData"
|
||||
[filter]="filterService.getFilter$('statusFilters') | async"
|
||||
[radius]="80"
|
||||
[strokeWidth]="15"
|
||||
[subtitle]="'dossier-listing.stats.charts.total-documents'"
|
||||
|
||||
@ -9,9 +9,9 @@ import { FilterService } from '@shared/services/filter.service';
|
||||
styleUrls: ['./dossier-listing-details.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class DossierListingDetailsComponent<T> {
|
||||
export class DossierListingDetailsComponent {
|
||||
@Input() dossiersChartData: DoughnutChartConfig[];
|
||||
@Input() documentsChartData: DoughnutChartConfig[];
|
||||
|
||||
constructor(readonly appStateService: AppStateService, readonly filterService: FilterService<T>) {}
|
||||
constructor(readonly appStateService: AppStateService, readonly filterService: FilterService) {}
|
||||
}
|
||||
|
||||
@ -2,9 +2,9 @@ import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from
|
||||
import { PermissionsService } from '@services/permissions.service';
|
||||
import { FormBuilder, FormGroup } from '@angular/forms';
|
||||
import { PageRange, ReanalysisControllerService } from '@redaction/red-ui-http';
|
||||
import { FileDataModel } from '@models/file/file-data.model';
|
||||
import { NotificationService, NotificationType } from '@services/notification.service';
|
||||
import { LoadingService } from '@services/loading.service';
|
||||
import { FileDataModel } from '../../../../models/file/file-data.model';
|
||||
import { Toaster } from '../../../../services/toaster.service';
|
||||
import { LoadingService } from '../../../../services/loading.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
@Component({
|
||||
@ -23,9 +23,8 @@ export class PageExclusionComponent implements OnChanges {
|
||||
readonly permissionsService: PermissionsService,
|
||||
private readonly _formBuilder: FormBuilder,
|
||||
private readonly _reanalysisControllerService: ReanalysisControllerService,
|
||||
private readonly _notificationService: NotificationService,
|
||||
private readonly _loadingService: LoadingService,
|
||||
private readonly _translateService: TranslateService
|
||||
private readonly _toaster: Toaster,
|
||||
private readonly _loadingService: LoadingService
|
||||
) {
|
||||
this.excludePagesForm = this._formBuilder.group({
|
||||
value: ['']
|
||||
@ -80,11 +79,7 @@ export class PageExclusionComponent implements OnChanges {
|
||||
this.excludePagesForm.reset();
|
||||
this.actionPerformed.emit('exclude-pages');
|
||||
} catch (e) {
|
||||
this._notificationService.showToastNotification(
|
||||
this._translateService.instant('file-preview.tabs.exclude-pages.error'),
|
||||
null,
|
||||
NotificationType.ERROR
|
||||
);
|
||||
this._toaster.error('file-preview.tabs.exclude-pages.error');
|
||||
this._loadingService.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
||||
import { Dossier, DossierControllerService, StatusControllerService } from '@redaction/red-ui-http';
|
||||
import { Dossier } from '@redaction/red-ui-http';
|
||||
import { AppStateService } from '@state/app-state.service';
|
||||
import { UserService } from '@services/user.service';
|
||||
import { NotificationService, NotificationType } from '@services/notification.service';
|
||||
import { Toaster } from '../../../../services/toaster.service';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { DossierWrapper } from '@state/model/dossier.wrapper';
|
||||
|
||||
@ -21,10 +21,8 @@ export class TeamMembersManagerComponent implements OnInit {
|
||||
|
||||
constructor(
|
||||
readonly userService: UserService,
|
||||
private readonly _dossierControllerService: DossierControllerService,
|
||||
private readonly _notificationService: NotificationService,
|
||||
private readonly _toaster: Toaster,
|
||||
private readonly _formBuilder: FormBuilder,
|
||||
private readonly _statusControllerService: StatusControllerService,
|
||||
private readonly _appStateService: AppStateService
|
||||
) {}
|
||||
|
||||
@ -88,14 +86,10 @@ export class TeamMembersManagerComponent implements OnInit {
|
||||
dw.dossier.memberIds = memberIds;
|
||||
dw.dossier.approverIds = approverIds;
|
||||
dw.dossier.ownerId = ownerId;
|
||||
result = await this._appStateService.addOrUpdateDossier(dw.dossier);
|
||||
result = await this._appStateService.createOrUpdateDossier(dw.dossier);
|
||||
this.save.emit(result);
|
||||
} catch (error) {
|
||||
this._notificationService.showToastNotification(
|
||||
'Failed: ' + error.error ? error.error.message : error,
|
||||
null,
|
||||
NotificationType.ERROR
|
||||
);
|
||||
this._toaster.error('Failed: ' + error.error ? error.error.message : error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -64,7 +64,7 @@ export class AddDossierDialogComponent {
|
||||
dossier.memberIds = foundDossier.memberIds;
|
||||
}
|
||||
|
||||
const savedDossier = await this._appStateService.addOrUpdateDossier(dossier);
|
||||
const savedDossier = await this._appStateService.createOrUpdateDossier(dossier);
|
||||
if (savedDossier) {
|
||||
this.dialogRef.close({ dossier: savedDossier });
|
||||
}
|
||||
@ -72,7 +72,7 @@ export class AddDossierDialogComponent {
|
||||
|
||||
async saveDossierAndAddMembers() {
|
||||
const dossier: Dossier = this._formToObject();
|
||||
const savedDossier = await this._appStateService.addOrUpdateDossier(dossier);
|
||||
const savedDossier = await this._appStateService.createOrUpdateDossier(dossier);
|
||||
if (savedDossier) {
|
||||
this.dialogRef.close({ addMembers: true, dossier: savedDossier });
|
||||
}
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import { Component, Inject } from '@angular/core';
|
||||
import { DossierControllerService, StatusControllerService } from '@redaction/red-ui-http';
|
||||
import { StatusControllerService } from '@redaction/red-ui-http';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { AppStateService } from '@state/app-state.service';
|
||||
import { UserService } from '@services/user.service';
|
||||
import { NotificationService, NotificationType } from '@services/notification.service';
|
||||
import { Toaster } from '../../../../services/toaster.service';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { FileStatusWrapper } from '@models/file/file-status.wrapper';
|
||||
import { DossierWrapper } from '@state/model/dossier.wrapper';
|
||||
@ -16,7 +16,6 @@ class DialogData {
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-dossier-details-dialog',
|
||||
templateUrl: './assign-reviewer-approver-dialog.component.html',
|
||||
styleUrls: ['./assign-reviewer-approver-dialog.component.scss']
|
||||
})
|
||||
@ -26,13 +25,12 @@ export class AssignReviewerApproverDialogComponent {
|
||||
|
||||
constructor(
|
||||
readonly userService: UserService,
|
||||
private readonly _dossierControllerService: DossierControllerService,
|
||||
private readonly _notificationService: NotificationService,
|
||||
private readonly _toaster: Toaster,
|
||||
private readonly _formBuilder: FormBuilder,
|
||||
private readonly _statusControllerService: StatusControllerService,
|
||||
private readonly _appStateService: AppStateService,
|
||||
public dialogRef: MatDialogRef<AssignReviewerApproverDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public data: DialogData
|
||||
private readonly _dialogRef: MatDialogRef<AssignReviewerApproverDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) readonly data: DialogData
|
||||
) {
|
||||
this._loadData();
|
||||
}
|
||||
@ -52,10 +50,8 @@ export class AssignReviewerApproverDialogComponent {
|
||||
return true;
|
||||
}
|
||||
|
||||
const reviewerId = this.selectedSingleUser;
|
||||
|
||||
for (const file of this.data.files) {
|
||||
if (file.currentReviewer !== reviewerId) {
|
||||
if (file.currentReviewer !== this.selectedSingleUser) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -94,14 +90,10 @@ export class AssignReviewerApproverDialogComponent {
|
||||
file.reviewerName = this.userService.getNameForId(selectedUser);
|
||||
}
|
||||
} catch (error) {
|
||||
this._notificationService.showToastNotification(
|
||||
'Failed: ' + error.error ? error.error.message : error,
|
||||
null,
|
||||
NotificationType.ERROR
|
||||
);
|
||||
this._toaster.error('Failed: ' + error.error ? error.error.message : error);
|
||||
}
|
||||
|
||||
this.dialogRef.close();
|
||||
this._dialogRef.close();
|
||||
}
|
||||
|
||||
private _loadData() {
|
||||
|
||||
@ -71,7 +71,7 @@ export class EditDossierDownloadPackageComponent implements OnInit, EditDossierS
|
||||
downloadFileTypes: this.dossierForm.get('downloadFileTypes').value,
|
||||
reportTypes: this.dossierForm.get('reportTypes').value
|
||||
};
|
||||
const updatedDossier = await this._appStateService.addOrUpdateDossier(dossier);
|
||||
const updatedDossier = await this._appStateService.createOrUpdateDossier(dossier);
|
||||
this.updateDossier.emit(updatedDossier);
|
||||
}
|
||||
|
||||
|
||||
@ -6,8 +6,7 @@ import { DossierWrapper } from '@state/model/dossier.wrapper';
|
||||
import { EditDossierGeneralInfoComponent } from './general-info/edit-dossier-general-info.component';
|
||||
import { EditDossierDownloadPackageComponent } from './download-package/edit-dossier-download-package.component';
|
||||
import { EditDossierSectionInterface } from './edit-dossier-section.interface';
|
||||
import { NotificationService, NotificationType } from '@services/notification.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Toaster } from '../../../../services/toaster.service';
|
||||
import { EditDossierDictionaryComponent } from './dictionary/edit-dossier-dictionary.component';
|
||||
import { EditDossierTeamMembersComponent } from './team-members/edit-dossier-team-members.component';
|
||||
import { EditDossierAttributesComponent } from './attributes/edit-dossier-attributes.component';
|
||||
@ -32,12 +31,8 @@ export class EditDossierDialogComponent {
|
||||
@ViewChild(EditDossierAttributesComponent) attributesComponent: EditDossierAttributesComponent;
|
||||
|
||||
constructor(
|
||||
private readonly _appStateService: AppStateService,
|
||||
private readonly _formBuilder: FormBuilder,
|
||||
private readonly _notificationService: NotificationService,
|
||||
private readonly _translateService: TranslateService,
|
||||
private readonly _toaster: Toaster,
|
||||
private readonly _changeRef: ChangeDetectorRef,
|
||||
private readonly _dialogRef: MatDialogRef<EditDossierDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA)
|
||||
private readonly _data: {
|
||||
dossierWrapper: DossierWrapper;
|
||||
@ -102,11 +97,7 @@ export class EditDossierDialogComponent {
|
||||
}
|
||||
|
||||
updatedDossier(updatedDossier: DossierWrapper) {
|
||||
this._notificationService.showToastNotification(
|
||||
this._translateService.instant('edit-dossier-dialog.change-successful'),
|
||||
null,
|
||||
NotificationType.SUCCESS
|
||||
);
|
||||
this._toaster.success('edit-dossier-dialog.change-successful');
|
||||
|
||||
if (updatedDossier) {
|
||||
this.dossierWrapper = updatedDossier;
|
||||
@ -131,11 +122,7 @@ export class EditDossierDialogComponent {
|
||||
|
||||
changeTab(key: Section) {
|
||||
if (this.activeComponent.changed) {
|
||||
this._notificationService.showToastNotification(
|
||||
this._translateService.instant('edit-dossier-dialog.unsaved-changes'),
|
||||
null,
|
||||
NotificationType.ERROR
|
||||
);
|
||||
this._toaster.error('edit-dossier-dialog.unsaved-changes');
|
||||
return;
|
||||
}
|
||||
this.activeNav = key;
|
||||
|
||||
@ -10,8 +10,7 @@ import { PermissionsService } from '@services/permissions.service';
|
||||
import { Router } from '@angular/router';
|
||||
import { MatDialogRef } from '@angular/material/dialog';
|
||||
import { EditDossierDialogComponent } from '../edit-dossier-dialog.component';
|
||||
import { NotificationService, NotificationType } from '@services/notification.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Toaster } from '../../../../../services/toaster.service';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-edit-dossier-general-info',
|
||||
@ -34,8 +33,7 @@ export class EditDossierGeneralInfoComponent implements OnInit, EditDossierSecti
|
||||
private readonly _dialogService: DossiersDialogService,
|
||||
private readonly _router: Router,
|
||||
private readonly _editDossierDialogRef: MatDialogRef<EditDossierDialogComponent>,
|
||||
private readonly _notificationService: NotificationService,
|
||||
private readonly _translateService: TranslateService
|
||||
private readonly _toaster: Toaster
|
||||
) {}
|
||||
|
||||
get changed() {
|
||||
@ -100,7 +98,7 @@ export class EditDossierGeneralInfoComponent implements OnInit, EditDossierSecti
|
||||
dueDate: this.hasDueDate ? this.dossierForm.get('dueDate').value : undefined,
|
||||
dossierTemplateId: this.dossierForm.get('dossierTemplateId').value
|
||||
};
|
||||
const updatedDossier = await this._appStateService.addOrUpdateDossier(dossier);
|
||||
const updatedDossier = await this._appStateService.createOrUpdateDossier(dossier);
|
||||
if (updatedDossier) this.updateDossier.emit(updatedDossier);
|
||||
}
|
||||
|
||||
@ -113,11 +111,7 @@ export class EditDossierGeneralInfoComponent implements OnInit, EditDossierSecti
|
||||
}
|
||||
|
||||
private _notifyDossierDeleted() {
|
||||
this._notificationService.showToastNotification(
|
||||
this._translateService.instant('edit-dossier-dialog.delete-successful'),
|
||||
null,
|
||||
NotificationType.SUCCESS
|
||||
);
|
||||
this._toaster.success('edit-dossier-dialog.delete-successful');
|
||||
}
|
||||
|
||||
private _filterInvalidDossierTemplates() {
|
||||
|
||||
@ -3,7 +3,7 @@ import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { AppStateService } from '@state/app-state.service';
|
||||
import { MatDialogRef } from '@angular/material/dialog';
|
||||
import { ForceRedactionRequest, LegalBasisMappingControllerService } from '@redaction/red-ui-http';
|
||||
import { NotificationService } from '@services/notification.service';
|
||||
import { Toaster } from '../../../../services/toaster.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { UserService } from '@services/user.service';
|
||||
import { ManualAnnotationService } from '../../services/manual-annotation.service';
|
||||
@ -29,7 +29,7 @@ export class ForceRedactionDialogComponent implements OnInit {
|
||||
private readonly _appStateService: AppStateService,
|
||||
private readonly _userService: UserService,
|
||||
private readonly _formBuilder: FormBuilder,
|
||||
private readonly _notificationService: NotificationService,
|
||||
private readonly _notificationService: Toaster,
|
||||
private readonly _translateService: TranslateService,
|
||||
private readonly _legalBasisMappingControllerService: LegalBasisMappingControllerService,
|
||||
private readonly _manualAnnotationService: ManualAnnotationService,
|
||||
|
||||
@ -3,7 +3,7 @@ import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { AppStateService } from '@state/app-state.service';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { AddRedactionRequest, LegalBasisMappingControllerService } from '@redaction/red-ui-http';
|
||||
import { NotificationService } from '@services/notification.service';
|
||||
import { Toaster } from '../../../../services/toaster.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { UserService } from '@services/user.service';
|
||||
import { ManualRedactionEntryWrapper } from '@models/file/manual-redaction-entry.wrapper';
|
||||
@ -37,7 +37,7 @@ export class ManualAnnotationDialogComponent implements OnInit {
|
||||
private readonly _appStateService: AppStateService,
|
||||
private readonly _userService: UserService,
|
||||
private readonly _formBuilder: FormBuilder,
|
||||
private readonly _notificationService: NotificationService,
|
||||
private readonly _notificationService: Toaster,
|
||||
private readonly _translateService: TranslateService,
|
||||
private readonly _legalBasisMappingControllerService: LegalBasisMappingControllerService,
|
||||
private readonly _manualAnnotationService: ManualAnnotationService,
|
||||
|
||||
@ -8,52 +8,25 @@
|
||||
|
||||
<div class="red-content-inner">
|
||||
<div class="content-container">
|
||||
<div class="header-item">
|
||||
<span class="all-caps-label">
|
||||
{{ 'dossier-listing.table-header.title' | translate: { length: (displayedEntities$ | async)?.length || 0 } }}
|
||||
</span>
|
||||
|
||||
<redaction-quick-filters></redaction-quick-filters>
|
||||
</div>
|
||||
|
||||
<div class="table-header" redactionSyncWidth="table-item">
|
||||
<redaction-table-col-name
|
||||
(toggleSort)="toggleSort($event)"
|
||||
[activeSortingOption]="sortingOption"
|
||||
[label]="'dossier-listing.table-col-names.name' | translate"
|
||||
[withSort]="true"
|
||||
column="dossier.dossierName"
|
||||
></redaction-table-col-name>
|
||||
|
||||
<redaction-table-col-name [label]="'dossier-listing.table-col-names.needs-work' | translate"></redaction-table-col-name>
|
||||
|
||||
<redaction-table-col-name
|
||||
[label]="'dossier-listing.table-col-names.owner' | translate"
|
||||
class="user-column"
|
||||
></redaction-table-col-name>
|
||||
|
||||
<redaction-table-col-name
|
||||
[label]="'dossier-listing.table-col-names.status' | translate"
|
||||
class="flex-end"
|
||||
></redaction-table-col-name>
|
||||
|
||||
<div class="scrollbar-placeholder"></div>
|
||||
</div>
|
||||
<redaction-table-header
|
||||
[tableHeaderLabel]="'dossier-listing.table-header.title'"
|
||||
[tableColConfigs]="tableColConfigs"
|
||||
></redaction-table-header>
|
||||
|
||||
<redaction-empty-state
|
||||
(action)="openAddDossierDialog()"
|
||||
*ngIf="noData"
|
||||
*ngIf="screenStateService.noData$ | async"
|
||||
[buttonLabel]="'dossier-listing.no-data.action' | translate"
|
||||
[showButton]="permissionsService.isManager()"
|
||||
[text]="'dossier-listing.no-data.title' | translate"
|
||||
icon="red:folder"
|
||||
></redaction-empty-state>
|
||||
|
||||
<redaction-empty-state *ngIf="noMatch" [text]="'dossier-listing.no-match.title' | translate"></redaction-empty-state>
|
||||
<redaction-empty-state *ngIf="noMatch$ | async" [text]="'dossier-listing.no-match.title' | translate"></redaction-empty-state>
|
||||
|
||||
<cdk-virtual-scroll-viewport [itemSize]="itemSize" redactionHasScrollbar>
|
||||
<div
|
||||
*cdkVirtualFor="let dw of displayedEntities$ | async | sortBy: sortingOption.order:sortingOption.column"
|
||||
*cdkVirtualFor="let dw of sortedDisplayedEntities$ | async; trackBy: trackByPrimaryKey"
|
||||
[class.pointer]="!!dw"
|
||||
[routerLink]="[!!dw ? '/main/dossiers/' + dw.dossier.dossierId : []]"
|
||||
class="table-item"
|
||||
@ -112,7 +85,7 @@
|
||||
|
||||
<div class="right-container" redactionHasScrollbar>
|
||||
<redaction-dossier-listing-details
|
||||
*ngIf="(allEntities$ | async)?.length !== 0"
|
||||
*ngIf="(screenStateService.noData$ | async) === false"
|
||||
[documentsChartData]="documentsChartData"
|
||||
[dossiersChartData]="dossiersChartData"
|
||||
></redaction-dossier-listing-details>
|
||||
|
||||
@ -1,14 +1,13 @@
|
||||
import { Component, Injector, OnDestroy, OnInit, TemplateRef, ViewChild } from '@angular/core';
|
||||
import { AfterViewInit, ChangeDetectorRef, Component, Injector, OnDestroy, OnInit, TemplateRef, ViewChild } from '@angular/core';
|
||||
import { Dossier, DossierTemplateModel } from '@redaction/red-ui-http';
|
||||
import { AppStateService } from '@state/app-state.service';
|
||||
import { UserService } from '@services/user.service';
|
||||
import { DoughnutChartConfig } from '@shared/components/simple-doughnut-chart/simple-doughnut-chart.component';
|
||||
import { groupBy } from '@utils/functions';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { PermissionsService } from '@services/permissions.service';
|
||||
import { DossierWrapper } from '@state/model/dossier.wrapper';
|
||||
import { Subscription, timer } from 'rxjs';
|
||||
import { filter, tap } from 'rxjs/operators';
|
||||
import { timer } from 'rxjs';
|
||||
import { filter } from 'rxjs/operators';
|
||||
import { TranslateChartService } from '@services/translate-chart.service';
|
||||
import { RedactionFilterSorter } from '@utils/sorters/redaction-filter-sorter';
|
||||
import { StatusSorter } from '@utils/sorters/status-sorter';
|
||||
@ -28,16 +27,22 @@ import { FilterService } from '@shared/services/filter.service';
|
||||
import { SearchService } from '@shared/services/search.service';
|
||||
import { ScreenStateService } from '@shared/services/screen-state.service';
|
||||
import { BaseListingComponent } from '@shared/base/base-listing.component';
|
||||
import { ScreenNames, SortingService } from '@services/sorting.service';
|
||||
import { SortingService } from '@services/sorting.service';
|
||||
import { TableColConfig } from '../../../shared/components/table-col-name/table-col-name.component';
|
||||
|
||||
const isLeavingScreen = event => event instanceof NavigationStart && event.url !== '/main/dossiers';
|
||||
|
||||
@Component({
|
||||
templateUrl: './dossier-listing-screen.component.html',
|
||||
styleUrls: ['./dossier-listing-screen.component.scss'],
|
||||
providers: [FilterService, SearchService, ScreenStateService, SortingService]
|
||||
})
|
||||
export class DossierListingScreenComponent extends BaseListingComponent<DossierWrapper> implements OnInit, OnDestroy, OnAttach, OnDetach {
|
||||
dossiersChartData: DoughnutChartConfig[] = [];
|
||||
documentsChartData: DoughnutChartConfig[] = [];
|
||||
export class DossierListingScreenComponent
|
||||
extends BaseListingComponent<DossierWrapper>
|
||||
implements OnInit, AfterViewInit, OnDestroy, OnAttach, OnDetach
|
||||
{
|
||||
readonly itemSize = 95;
|
||||
protected readonly _primaryKey = 'dossierName';
|
||||
buttonConfigs: ButtonConfig[] = [
|
||||
{
|
||||
label: this._translateService.instant('dossier-listing.add-new'),
|
||||
@ -47,19 +52,34 @@ export class DossierListingScreenComponent extends BaseListingComponent<DossierW
|
||||
type: 'primary'
|
||||
}
|
||||
];
|
||||
tableColConfigs: TableColConfig[] = [
|
||||
{
|
||||
label: this._translateService.instant('dossier-listing.table-col-names.name'),
|
||||
withSort: true,
|
||||
column: 'dossierName'
|
||||
},
|
||||
{
|
||||
label: this._translateService.instant('dossier-listing.table-col-names.needs-work')
|
||||
},
|
||||
{
|
||||
label: this._translateService.instant('dossier-listing.table-col-names.owner'),
|
||||
class: 'user-column'
|
||||
},
|
||||
{
|
||||
label: this._translateService.instant('dossier-listing.table-col-names.status'),
|
||||
class: 'flex-end'
|
||||
}
|
||||
];
|
||||
|
||||
readonly itemSize = 85;
|
||||
dossiersChartData: DoughnutChartConfig[] = [];
|
||||
documentsChartData: DoughnutChartConfig[] = [];
|
||||
|
||||
private _dossierAutoUpdateTimer: Subscription;
|
||||
private _lastScrollPosition: number;
|
||||
private _routerEventsScrollPositionSub: Subscription;
|
||||
private _fileChangedSub: Subscription;
|
||||
|
||||
@ViewChild('needsWorkTemplate', { read: TemplateRef, static: true })
|
||||
private readonly _needsWorkTemplate: TemplateRef<any>;
|
||||
|
||||
constructor(
|
||||
readonly permissionsService: PermissionsService,
|
||||
private readonly _translateChartService: TranslateChartService,
|
||||
private readonly _userService: UserService,
|
||||
private readonly _dialogService: DossiersDialogService,
|
||||
@ -67,70 +87,47 @@ export class DossierListingScreenComponent extends BaseListingComponent<DossierW
|
||||
private readonly _router: Router,
|
||||
private readonly _appStateService: AppStateService,
|
||||
private readonly _userPreferenceService: UserPreferenceService,
|
||||
readonly changeDetectorRef: ChangeDetectorRef,
|
||||
protected readonly _injector: Injector
|
||||
) {
|
||||
super(_injector);
|
||||
this._sortingService.setScreenName(ScreenNames.DOSSIER_LISTING);
|
||||
this._searchService.setSearchKey('name');
|
||||
this._appStateService.reset();
|
||||
this._loadEntitiesFromState();
|
||||
}
|
||||
|
||||
private get _user() {
|
||||
return this._userService.user;
|
||||
}
|
||||
|
||||
private get _activeDossiersCount(): number {
|
||||
return this._screenStateService.entities.filter(p => p.dossier.status === Dossier.StatusEnum.ACTIVE).length;
|
||||
}
|
||||
|
||||
private get _inactiveDossiersCount(): number {
|
||||
return this._screenStateService.entities.length - this._activeDossiersCount;
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.calculateData();
|
||||
|
||||
this._dossierAutoUpdateTimer = timer(0, 10000)
|
||||
.pipe(
|
||||
tap(async () => {
|
||||
await this._appStateService.loadAllDossiers();
|
||||
this._loadEntitiesFromState();
|
||||
this.calculateData();
|
||||
})
|
||||
)
|
||||
.subscribe();
|
||||
|
||||
this._fileChangedSub = this._appStateService.fileChanged.subscribe(() => {
|
||||
this.addSubscription = timer(0, 10000).subscribe(async () => {
|
||||
await this._appStateService.loadAllDossiers();
|
||||
this._loadEntitiesFromState();
|
||||
this.calculateData();
|
||||
});
|
||||
|
||||
this._routerEventsScrollPositionSub = this._router.events
|
||||
.pipe(filter(event => event instanceof NavigationStart))
|
||||
.subscribe((event: NavigationStart) => {
|
||||
if (event.url !== '/main/dossiers') {
|
||||
this._lastScrollPosition = this.scrollViewport.measureScrollOffset('top');
|
||||
}
|
||||
});
|
||||
this.addSubscription = this._appStateService.fileChanged.subscribe(() => {
|
||||
this.calculateData();
|
||||
});
|
||||
|
||||
this.addSubscription = this._router.events.pipe(filter(isLeavingScreen)).subscribe(() => {
|
||||
this._lastScrollPosition = this.scrollViewport.measureScrollOffset('top');
|
||||
});
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
this.changeDetectorRef.detectChanges();
|
||||
}
|
||||
|
||||
ngOnAttach() {
|
||||
this.scrollViewport.scrollTo({ top: this._lastScrollPosition });
|
||||
this._appStateService.reset();
|
||||
this._loadEntitiesFromState();
|
||||
this.ngOnInit();
|
||||
this.scrollViewport.scrollTo({ top: this._lastScrollPosition });
|
||||
}
|
||||
|
||||
ngOnDetach() {
|
||||
ngOnDetach(): void {
|
||||
this.ngOnDestroy();
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this._dossierAutoUpdateTimer.unsubscribe();
|
||||
this._routerEventsScrollPositionSub.unsubscribe();
|
||||
this._fileChangedSub.unsubscribe();
|
||||
}
|
||||
|
||||
getDossierTemplate(dw: DossierWrapper): DossierTemplateModel {
|
||||
return this._appStateService.getDossierTemplateById(dw.dossier.dossierTemplateId);
|
||||
}
|
||||
@ -147,6 +144,18 @@ export class DossierListingScreenComponent extends BaseListingComponent<DossierW
|
||||
});
|
||||
}
|
||||
|
||||
private get _userId() {
|
||||
return this._userService.userId;
|
||||
}
|
||||
|
||||
private get _activeDossiersCount(): number {
|
||||
return this.screenStateService.allEntities.filter(p => p.dossier.status === Dossier.StatusEnum.ACTIVE).length;
|
||||
}
|
||||
|
||||
private get _inactiveDossiersCount(): number {
|
||||
return this.screenStateService.allEntities.length - this._activeDossiersCount;
|
||||
}
|
||||
|
||||
calculateData() {
|
||||
this._computeAllFilters();
|
||||
|
||||
@ -165,12 +174,12 @@ export class DossierListingScreenComponent extends BaseListingComponent<DossierW
|
||||
key: key
|
||||
});
|
||||
}
|
||||
this.documentsChartData.sort((a, b) => StatusSorter[a.key] - StatusSorter[b.key]);
|
||||
this.documentsChartData.sort(StatusSorter.byStatus);
|
||||
this.documentsChartData = this._translateChartService.translateStatus(this.documentsChartData);
|
||||
}
|
||||
|
||||
private _loadEntitiesFromState() {
|
||||
this._screenStateService.setEntities(this._appStateService.allDossiers);
|
||||
this.screenStateService.setEntities(this._appStateService.allDossiers);
|
||||
}
|
||||
|
||||
private _computeAllFilters() {
|
||||
@ -179,7 +188,7 @@ export class DossierListingScreenComponent extends BaseListingComponent<DossierW
|
||||
const allDistinctNeedsWork = new Set<string>();
|
||||
const allDistinctDossierTemplates = new Set<string>();
|
||||
|
||||
this._screenStateService?.entities?.forEach(entry => {
|
||||
this.screenStateService?.allEntities?.forEach(entry => {
|
||||
// all people
|
||||
entry.dossier.memberIds.forEach(f => allDistinctPeople.add(f));
|
||||
// Needs work
|
||||
@ -200,11 +209,11 @@ export class DossierListingScreenComponent extends BaseListingComponent<DossierW
|
||||
label: this._translateService.instant(status)
|
||||
}));
|
||||
|
||||
this.filterService.addFilter({
|
||||
this.filterService.addFilterGroup({
|
||||
slug: 'statusFilters',
|
||||
label: this._translateService.instant('filters.status'),
|
||||
icon: 'red:status',
|
||||
values: statusFilters.sort(StatusSorter.byKey),
|
||||
values: statusFilters.sort(StatusSorter.byStatus),
|
||||
checker: dossierStatusChecker
|
||||
});
|
||||
|
||||
@ -213,7 +222,7 @@ export class DossierListingScreenComponent extends BaseListingComponent<DossierW
|
||||
label: this._userService.getNameForId(userId)
|
||||
}));
|
||||
|
||||
this.filterService.addFilter({
|
||||
this.filterService.addFilterGroup({
|
||||
slug: 'peopleFilters',
|
||||
label: this._translateService.instant('filters.people'),
|
||||
icon: 'red:user',
|
||||
@ -226,7 +235,7 @@ export class DossierListingScreenComponent extends BaseListingComponent<DossierW
|
||||
label: `filter.${type}`
|
||||
}));
|
||||
|
||||
this.filterService.addFilter({
|
||||
this.filterService.addFilterGroup({
|
||||
slug: 'needsWorkFilters',
|
||||
label: this._translateService.instant('filters.needs-work'),
|
||||
icon: 'red:needs-work',
|
||||
@ -242,23 +251,21 @@ export class DossierListingScreenComponent extends BaseListingComponent<DossierW
|
||||
label: this._appStateService.getDossierTemplateById(id).name
|
||||
}));
|
||||
|
||||
this.filterService.addFilter({
|
||||
this.filterService.addFilterGroup({
|
||||
slug: 'dossierTemplateFilters',
|
||||
label: this._translateService.instant('filters.dossier-templates'),
|
||||
icon: 'red:template',
|
||||
hide: this.filterService.getFilter('dossierTemplateFilters')?.values?.length <= 1,
|
||||
hide: this.filterService.getFilterGroup('dossierTemplateFilters')?.values?.length <= 1,
|
||||
values: dossierTemplateFilters,
|
||||
checker: dossierTemplateChecker
|
||||
});
|
||||
|
||||
const quickFilters = this._createQuickFilters();
|
||||
this.filterService.addFilter({
|
||||
this.filterService.addFilterGroup({
|
||||
slug: 'quickFilters',
|
||||
values: quickFilters,
|
||||
checker: (dw: DossierWrapper) => quickFilters.reduce((acc, f) => acc || (f.checked && f.checker(dw)), false)
|
||||
});
|
||||
|
||||
this.filterService.filterEntities();
|
||||
}
|
||||
|
||||
private _createQuickFilters() {
|
||||
@ -267,22 +274,22 @@ export class DossierListingScreenComponent extends BaseListingComponent<DossierW
|
||||
{
|
||||
key: 'my-dossiers',
|
||||
label: myDossiersLabel,
|
||||
checker: (dw: DossierWrapper) => dw.ownerId === this._user.id
|
||||
checker: (dw: DossierWrapper) => dw.ownerId === this._userId
|
||||
},
|
||||
{
|
||||
key: 'to-approve',
|
||||
label: this._translateService.instant('dossier-listing.quick-filters.to-approve'),
|
||||
checker: (dw: DossierWrapper) => dw.approverIds.includes(this._user.id)
|
||||
checker: (dw: DossierWrapper) => dw.approverIds.includes(this._userId)
|
||||
},
|
||||
{
|
||||
key: 'to-review',
|
||||
label: this._translateService.instant('dossier-listing.quick-filters.to-review'),
|
||||
checker: (dw: DossierWrapper) => dw.memberIds.includes(this._user.id)
|
||||
checker: (dw: DossierWrapper) => dw.memberIds.includes(this._userId)
|
||||
},
|
||||
{
|
||||
key: 'other',
|
||||
label: this._translateService.instant('dossier-listing.quick-filters.other'),
|
||||
checker: (dw: DossierWrapper) => !dw.memberIds.includes(this._user.id)
|
||||
checker: (dw: DossierWrapper) => !dw.memberIds.includes(this._userId)
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@ -5,17 +5,17 @@
|
||||
[showCloseButton]="true"
|
||||
>
|
||||
<redaction-file-download-btn
|
||||
[disabled]="areSomeEntitiesSelected$ | async"
|
||||
[disabled]="screenStateService.areSomeEntitiesSelected$ | async"
|
||||
[dossier]="activeDossier"
|
||||
[file]="allEntities$ | async"
|
||||
[file]="screenStateService.allEntities$ | async"
|
||||
tooltipPosition="below"
|
||||
></redaction-file-download-btn>
|
||||
|
||||
<redaction-circle-button
|
||||
(action)="reanalyseDossier()"
|
||||
*ngIf="permissionsService.displayReanalyseBtn()"
|
||||
[disabled]="areSomeEntitiesSelected$ | async"
|
||||
[tooltipClass]="'small ' + ((areSomeEntitiesSelected$ | async) ? '' : 'warn')"
|
||||
[disabled]="screenStateService.areSomeEntitiesSelected$ | async"
|
||||
[tooltipClass]="'small ' + ((screenStateService.areSomeEntitiesSelected$ | async) ? '' : 'warn')"
|
||||
[tooltip]="'dossier-overview.new-rule.toast.actions.reanalyse-all' | translate"
|
||||
icon="red:refresh"
|
||||
tooltipPosition="below"
|
||||
@ -40,38 +40,31 @@
|
||||
<div class="select-all-container">
|
||||
<redaction-round-checkbox
|
||||
(click)="toggleSelectAll()"
|
||||
[active]="areAllEntitiesSelected"
|
||||
[indeterminate]="(areSomeEntitiesSelected$ | async) && !areAllEntitiesSelected"
|
||||
[active]="screenStateService.areAllEntitiesSelected$ | async"
|
||||
[indeterminate]="screenStateService.notAllEntitiesSelected$ | async"
|
||||
></redaction-round-checkbox>
|
||||
</div>
|
||||
|
||||
<span class="all-caps-label">
|
||||
{{ 'dossier-overview.table-header.title' | translate: { length: (displayedEntities$ | async)?.length || 0 } }}
|
||||
{{ 'dossier-overview.table-header.title' | translate: { length: (screenStateService.displayedLength$ | async) || 0 } }}
|
||||
</span>
|
||||
|
||||
<redaction-dossier-overview-bulk-actions
|
||||
(reload)="bulkActionPerformed()"
|
||||
[selectedFileIds]="selectedEntitiesIds$ | async"
|
||||
></redaction-dossier-overview-bulk-actions>
|
||||
<redaction-dossier-overview-bulk-actions (reload)="bulkActionPerformed()"></redaction-dossier-overview-bulk-actions>
|
||||
|
||||
<redaction-quick-filters></redaction-quick-filters>
|
||||
</div>
|
||||
|
||||
<div [class.no-data]="noData" class="table-header" redactionSyncWidth="table-item">
|
||||
<div [class.no-data]="screenStateService.noData$ | async" class="table-header" redactionSyncWidth="table-item">
|
||||
<!-- Table column names-->
|
||||
<div class="select-oval-placeholder"></div>
|
||||
|
||||
<redaction-table-col-name
|
||||
(toggleSort)="toggleSort($event)"
|
||||
[activeSortingOption]="sortingOption"
|
||||
[label]="'dossier-overview.table-col-names.name' | translate"
|
||||
[withSort]="true"
|
||||
column="filename"
|
||||
></redaction-table-col-name>
|
||||
|
||||
<redaction-table-col-name
|
||||
(toggleSort)="toggleSort($event)"
|
||||
[activeSortingOption]="sortingOption"
|
||||
[label]="'dossier-overview.table-col-names.added-on' | translate"
|
||||
[withSort]="true"
|
||||
column="added"
|
||||
@ -80,8 +73,6 @@
|
||||
<redaction-table-col-name [label]="'dossier-overview.table-col-names.needs-work' | translate"></redaction-table-col-name>
|
||||
|
||||
<redaction-table-col-name
|
||||
(toggleSort)="toggleSort($event)"
|
||||
[activeSortingOption]="sortingOption"
|
||||
[label]="'dossier-overview.table-col-names.assigned-to' | translate"
|
||||
[withSort]="true"
|
||||
class="user-column"
|
||||
@ -89,16 +80,12 @@
|
||||
></redaction-table-col-name>
|
||||
|
||||
<redaction-table-col-name
|
||||
(toggleSort)="toggleSort($event)"
|
||||
[activeSortingOption]="sortingOption"
|
||||
[label]="'dossier-overview.table-col-names.pages' | translate"
|
||||
[withSort]="true"
|
||||
column="pages"
|
||||
></redaction-table-col-name>
|
||||
|
||||
<redaction-table-col-name
|
||||
(toggleSort)="toggleSort($event)"
|
||||
[activeSortingOption]="sortingOption"
|
||||
[label]="'dossier-overview.table-col-names.status' | translate"
|
||||
[withSort]="true"
|
||||
class="flex-end"
|
||||
@ -109,21 +96,18 @@
|
||||
|
||||
<redaction-empty-state
|
||||
(action)="fileInput.click()"
|
||||
*ngIf="noData"
|
||||
*ngIf="screenStateService.noData$ | async"
|
||||
[buttonLabel]="'dossier-overview.no-data.action' | translate"
|
||||
[text]="'dossier-overview.no-data.title' | translate"
|
||||
buttonIcon="red:upload"
|
||||
icon="red:document"
|
||||
></redaction-empty-state>
|
||||
|
||||
<redaction-empty-state *ngIf="noMatch" [text]="'dossier-overview.no-match.title' | translate"></redaction-empty-state>
|
||||
<redaction-empty-state *ngIf="noMatch$ | async" [text]="'dossier-overview.no-match.title' | translate"></redaction-empty-state>
|
||||
|
||||
<cdk-virtual-scroll-viewport [itemSize]="itemSize" redactionHasScrollbar>
|
||||
<div
|
||||
*cdkVirtualFor="
|
||||
let fileStatus of displayedEntities$ | async | sortBy: sortingOption.order:sortingOption.column;
|
||||
trackBy: trackByFileId
|
||||
"
|
||||
*cdkVirtualFor="let fileStatus of sortedDisplayedEntities$ | async; trackBy: trackByPrimaryKey"
|
||||
[class.disabled]="fileStatus.isExcluded"
|
||||
[class.last-opened]="isLastOpenedFile(fileStatus)"
|
||||
[class.pointer]="permissionsService.canOpenFile(fileStatus)"
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { ChangeDetectorRef, Component, ElementRef, HostListener, Injector, OnDestroy, OnInit, TemplateRef, ViewChild } from '@angular/core';
|
||||
import { NavigationStart, Router } from '@angular/router';
|
||||
import { NotificationService, NotificationType } from '@services/notification.service';
|
||||
import { Toaster } from '../../../../services/toaster.service';
|
||||
import { AppStateService } from '@state/app-state.service';
|
||||
import { FileDropOverlayService } from '@upload-download/services/file-drop-overlay.service';
|
||||
import { FileUploadModel } from '@upload-download/model/file-upload.model';
|
||||
@ -10,11 +10,9 @@ 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 { PermissionsService } from '@services/permissions.service';
|
||||
import { UserService } from '@services/user.service';
|
||||
import { FileStatus, UserPreferenceControllerService } from '@redaction/red-ui-http';
|
||||
import { Subscription, timer } from 'rxjs';
|
||||
import { filter, tap } from 'rxjs/operators';
|
||||
import { timer } from 'rxjs';
|
||||
import { filter } from 'rxjs/operators';
|
||||
import { RedactionFilterSorter } from '@utils/sorters/redaction-filter-sorter';
|
||||
import { StatusSorter } from '@utils/sorters/status-sorter';
|
||||
import { convertFiles, handleFileDrop } from '@utils/file-drop-utils';
|
||||
@ -28,11 +26,12 @@ import { ActionConfig } from '@shared/components/page-header/models/action-confi
|
||||
import { FilterService } from '@shared/services/filter.service';
|
||||
import { SearchService } from '@shared/services/search.service';
|
||||
import { ScreenStateService } from '@shared/services/screen-state.service';
|
||||
import { ScreenNames, SortingService } from '@services/sorting.service';
|
||||
import { SortingService } from '../../../../services/sorting.service';
|
||||
import { BaseListingComponent } from '@shared/base/base-listing.component';
|
||||
import { LoadingService } from '@services/loading.service';
|
||||
import { DossierAttributesService } from '@shared/services/controller-wrappers/dossier-attributes.service';
|
||||
import { DossierAttributeWithValue } from '@models/dossier-attributes.model';
|
||||
import { UserPreferenceService } from '../../../../services/user-preference.service';
|
||||
|
||||
@Component({
|
||||
templateUrl: './dossier-overview-screen.component.html',
|
||||
@ -43,25 +42,23 @@ export class DossierOverviewScreenComponent
|
||||
extends BaseListingComponent<FileStatusWrapper>
|
||||
implements OnInit, OnDestroy, OnDetach, OnAttach
|
||||
{
|
||||
collapsedDetails = false;
|
||||
readonly itemSize = 80;
|
||||
protected readonly _primaryKey = 'filename';
|
||||
collapsedDetails = false;
|
||||
actionConfigs: ActionConfig[];
|
||||
dossierAttributes: DossierAttributeWithValue[] = [];
|
||||
@ViewChild(DossierDetailsComponent, { static: false })
|
||||
private readonly _dossierDetailsComponent: DossierDetailsComponent;
|
||||
private _filesAutoUpdateTimer: Subscription;
|
||||
private _routerEventsScrollPositionSub: Subscription;
|
||||
private _fileChangedSub: Subscription;
|
||||
private readonly _lastOpenedFileKey = 'Dossier-Recent-' + this.activeDossier.dossierId;
|
||||
private _lastScrollPosition: number;
|
||||
private _lastOpenedFileId = '';
|
||||
|
||||
@ViewChild('needsWorkTemplate', { read: TemplateRef, static: true })
|
||||
private readonly _needsWorkTemplate: TemplateRef<any>;
|
||||
@ViewChild('fileInput') private _fileInput: ElementRef;
|
||||
|
||||
constructor(
|
||||
readonly permissionsService: PermissionsService,
|
||||
private readonly _userService: UserService,
|
||||
private readonly _notificationService: NotificationService,
|
||||
private readonly _toaster: Toaster,
|
||||
private readonly _dialogService: DossiersDialogService,
|
||||
private readonly _fileUploadService: FileUploadService,
|
||||
private readonly _statusOverlayService: StatusOverlayService,
|
||||
@ -69,7 +66,7 @@ export class DossierOverviewScreenComponent
|
||||
private readonly _translateService: TranslateService,
|
||||
private readonly _fileDropOverlayService: FileDropOverlayService,
|
||||
private readonly _appStateService: AppStateService,
|
||||
private readonly _userPreferenceControllerService: UserPreferenceControllerService,
|
||||
private readonly _userPreferenceService: UserPreferenceService,
|
||||
private readonly _appConfigService: AppConfigService,
|
||||
private readonly _changeDetectorRef: ChangeDetectorRef,
|
||||
private readonly _loadingService: LoadingService,
|
||||
@ -77,9 +74,6 @@ export class DossierOverviewScreenComponent
|
||||
protected readonly _injector: Injector
|
||||
) {
|
||||
super(_injector);
|
||||
this._sortingService.setScreenName(ScreenNames.DOSSIER_OVERVIEW);
|
||||
this._searchService.setSearchKey('searchField');
|
||||
this._screenStateService.setIdKey('fileId');
|
||||
this._loadEntitiesFromState();
|
||||
}
|
||||
|
||||
@ -87,42 +81,38 @@ export class DossierOverviewScreenComponent
|
||||
return this._appStateService.activeDossier;
|
||||
}
|
||||
|
||||
get user() {
|
||||
return this._userService.user;
|
||||
get userId() {
|
||||
return this._userService.userId;
|
||||
}
|
||||
|
||||
get checkedRequiredFilters() {
|
||||
return this.filterService.getFilter('quickFilters')?.values.filter(f => f.required && f.checked);
|
||||
return this.filterService.getFilterGroup('quickFilters')?.values.filter(f => f.required && f.checked);
|
||||
}
|
||||
|
||||
get checkedNotRequiredFilters() {
|
||||
return this.filterService.getFilter('quickFilters')?.values.filter(f => !f.required && f.checked);
|
||||
return this.filterService.getFilterGroup('quickFilters')?.values.filter(f => !f.required && f.checked);
|
||||
}
|
||||
|
||||
isLastOpenedFile(fileStatus: FileStatusWrapper): boolean {
|
||||
return this._lastOpenedFileId === fileStatus.fileId;
|
||||
isLastOpenedFile({ fileId }: FileStatusWrapper): boolean {
|
||||
return this._userPreferenceService.getLastOpenedFileId(this._lastOpenedFileKey) === fileId;
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
async ngOnInit(): Promise<void> {
|
||||
this._fileDropOverlayService.initFileDropHandling();
|
||||
|
||||
this.calculateData();
|
||||
|
||||
this._filesAutoUpdateTimer = timer(0, 7500)
|
||||
.pipe(
|
||||
tap(async () => {
|
||||
await this._appStateService.reloadActiveDossierFilesIfNecessary();
|
||||
this._loadEntitiesFromState();
|
||||
})
|
||||
)
|
||||
.subscribe();
|
||||
this.addSubscription = timer(0, 7500).subscribe(async () => {
|
||||
await this._appStateService.reloadActiveDossierFilesIfNecessary();
|
||||
this._loadEntitiesFromState();
|
||||
});
|
||||
|
||||
this._fileChangedSub = this._appStateService.fileChanged.subscribe(() => {
|
||||
this.addSubscription = this._appStateService.fileChanged.subscribe(() => {
|
||||
this.calculateData();
|
||||
});
|
||||
|
||||
this._routerEventsScrollPositionSub = this._router.events
|
||||
.pipe(filter(events => events instanceof NavigationStart))
|
||||
this.addSubscription = this._router.events
|
||||
.pipe(filter(event => event instanceof NavigationStart))
|
||||
.subscribe((event: NavigationStart) => {
|
||||
if (!event.url.endsWith(this._appStateService.activeDossierId)) {
|
||||
this._lastScrollPosition = this.scrollViewport.measureScrollOffset('top');
|
||||
@ -131,13 +121,6 @@ export class DossierOverviewScreenComponent
|
||||
|
||||
this._loadingService.start();
|
||||
|
||||
const userAttributes = await this._userPreferenceControllerService.getAllUserAttributes();
|
||||
if (userAttributes === null || userAttributes === undefined) return;
|
||||
const key = 'Dossier-Recent-' + this.activeDossier.dossierId;
|
||||
if (userAttributes[key]?.length > 0) {
|
||||
this._lastOpenedFileId = userAttributes[key][0];
|
||||
}
|
||||
|
||||
this.dossierAttributes = await this._dossierAttributesService.getValues(this.activeDossier);
|
||||
|
||||
this._loadingService.stop();
|
||||
@ -145,9 +128,7 @@ export class DossierOverviewScreenComponent
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this._fileDropOverlayService.cleanupFileDropHandling();
|
||||
this._filesAutoUpdateTimer.unsubscribe();
|
||||
this._fileChangedSub.unsubscribe();
|
||||
this._routerEventsScrollPositionSub.unsubscribe();
|
||||
super.ngOnDestroy();
|
||||
}
|
||||
|
||||
async ngOnAttach() {
|
||||
@ -165,33 +146,13 @@ export class DossierOverviewScreenComponent
|
||||
.reanalyzeDossier()
|
||||
.then(() => {
|
||||
this.reloadDossiers();
|
||||
this._notificationService.showToastNotification(
|
||||
this._translateService.instant('dossier-overview.reanalyse-dossier.success'),
|
||||
null,
|
||||
NotificationType.SUCCESS
|
||||
);
|
||||
this._toaster.success('dossier-overview.reanalyse-dossier.success');
|
||||
})
|
||||
.catch(() => {
|
||||
this._notificationService.showToastNotification(
|
||||
this._translateService.instant('dossier-overview.reanalyse-dossier.error'),
|
||||
null,
|
||||
NotificationType.ERROR
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
isError(fileStatusWrapper: FileStatusWrapper) {
|
||||
return fileStatusWrapper.status === FileStatus.StatusEnum.ERROR;
|
||||
}
|
||||
|
||||
isProcessing(fileStatusWrapper: FileStatusWrapper) {
|
||||
return [FileStatus.StatusEnum.REPROCESS, FileStatus.StatusEnum.FULLREPROCESS, FileStatus.StatusEnum.PROCESSING].includes(
|
||||
fileStatusWrapper.status
|
||||
);
|
||||
.catch(() => this._toaster.error('dossier-overview.reanalyse-dossier.error'));
|
||||
}
|
||||
|
||||
reloadDossiers() {
|
||||
this._appStateService.getFiles(this._appStateService.activeDossier, false).then(() => {
|
||||
this._appStateService.getFiles(this.activeDossier, false).then(() => {
|
||||
this.calculateData();
|
||||
});
|
||||
}
|
||||
@ -202,16 +163,10 @@ export class DossierOverviewScreenComponent
|
||||
this._loadEntitiesFromState();
|
||||
this._computeAllFilters();
|
||||
|
||||
this.filterService.filterEntities();
|
||||
|
||||
this._dossierDetailsComponent?.calculateChartConfig();
|
||||
this._changeDetectorRef.detectChanges();
|
||||
}
|
||||
|
||||
trackByFileId(index: number, item: FileStatusWrapper) {
|
||||
return item.fileId;
|
||||
}
|
||||
|
||||
@HostListener('drop', ['$event'])
|
||||
onDrop(event: DragEvent) {
|
||||
handleFileDrop(event, this.activeDossier, this._uploadFiles.bind(this));
|
||||
@ -235,7 +190,7 @@ export class DossierOverviewScreenComponent
|
||||
}
|
||||
|
||||
bulkActionPerformed() {
|
||||
this._screenStateService.selectedEntitiesIds$.next([]);
|
||||
this.screenStateService.setSelectedEntities([]);
|
||||
this.reloadDossiers();
|
||||
}
|
||||
|
||||
@ -271,15 +226,12 @@ export class DossierOverviewScreenComponent
|
||||
moment(file.lastUpdated).add(this._appConfigService.getConfig(AppConfigKey.RECENT_PERIOD_IN_HOURS), 'hours').isAfter(moment());
|
||||
|
||||
private _loadEntitiesFromState() {
|
||||
if (this.activeDossier) this._screenStateService.setEntities(this.activeDossier.files);
|
||||
if (this.activeDossier) this.screenStateService.setEntities(this.activeDossier.files);
|
||||
}
|
||||
|
||||
private async _uploadFiles(files: FileUploadModel[]) {
|
||||
const fileCount = await this._fileUploadService.uploadFiles(files);
|
||||
if (fileCount) {
|
||||
this._statusOverlayService.openUploadStatusOverlay();
|
||||
}
|
||||
// this._changeDetectorRef.detectChanges();
|
||||
if (fileCount) this._statusOverlayService.openUploadStatusOverlay();
|
||||
}
|
||||
|
||||
private _computeAllFilters() {
|
||||
@ -290,7 +242,7 @@ export class DossierOverviewScreenComponent
|
||||
const allDistinctAddedDates = new Set<string>();
|
||||
const allDistinctNeedsWork = new Set<string>();
|
||||
|
||||
this._screenStateService.entities.forEach(file => {
|
||||
this.screenStateService.allEntities.forEach(file => {
|
||||
allDistinctPeople.add(file.currentReviewer);
|
||||
allDistinctFileStatusWrapper.add(file.status);
|
||||
allDistinctAddedDates.add(moment(file.added).format('DD/MM/YYYY'));
|
||||
@ -309,11 +261,11 @@ export class DossierOverviewScreenComponent
|
||||
label: this._translateService.instant(item)
|
||||
}));
|
||||
|
||||
this.filterService.addFilter({
|
||||
this.filterService.addFilterGroup({
|
||||
slug: 'statusFilters',
|
||||
label: this._translateService.instant('filters.status'),
|
||||
icon: 'red:status',
|
||||
values: statusFilters.sort(StatusSorter.byKey),
|
||||
values: statusFilters.sort(StatusSorter.byStatus),
|
||||
checker: keyChecker('status')
|
||||
});
|
||||
|
||||
@ -332,7 +284,7 @@ export class DossierOverviewScreenComponent
|
||||
label: this._userService.getNameForId(userId)
|
||||
});
|
||||
});
|
||||
this.filterService.addFilter({
|
||||
this.filterService.addFilterGroup({
|
||||
slug: 'peopleFilters',
|
||||
label: this._translateService.instant('filters.assigned-people'),
|
||||
icon: 'red:user',
|
||||
@ -345,7 +297,7 @@ export class DossierOverviewScreenComponent
|
||||
label: this._translateService.instant('filter.' + item)
|
||||
}));
|
||||
|
||||
this.filterService.addFilter({
|
||||
this.filterService.addFilterGroup({
|
||||
slug: 'needsWorkFilters',
|
||||
label: this._translateService.instant('filters.needs-work'),
|
||||
icon: 'red:needs-work',
|
||||
@ -356,7 +308,7 @@ export class DossierOverviewScreenComponent
|
||||
checkerArgs: this.permissionsService
|
||||
});
|
||||
|
||||
this.filterService.addFilter({
|
||||
this.filterService.addFilterGroup({
|
||||
slug: 'quickFilters',
|
||||
values: this._createQuickFilters(),
|
||||
checker: (file: FileStatusWrapper) =>
|
||||
@ -370,7 +322,7 @@ export class DossierOverviewScreenComponent
|
||||
|
||||
private _createQuickFilters() {
|
||||
let quickFilters = [];
|
||||
if (this._screenStateService.entities.filter(this.recentlyModifiedChecker).length > 0) {
|
||||
if (this.screenStateService.allEntities.filter(this.recentlyModifiedChecker).length > 0) {
|
||||
const recentPeriod = this._appConfigService.getConfig(AppConfigKey.RECENT_PERIOD_IN_HOURS);
|
||||
quickFilters = [
|
||||
{
|
||||
@ -389,7 +341,7 @@ export class DossierOverviewScreenComponent
|
||||
{
|
||||
key: 'assigned-to-me',
|
||||
label: this._translateService.instant('dossier-overview.quick-filters.assigned-to-me'),
|
||||
checker: (file: FileStatusWrapper) => file.currentReviewer === this.user.id
|
||||
checker: (file: FileStatusWrapper) => file.currentReviewer === this.userId
|
||||
},
|
||||
{
|
||||
key: 'unassigned',
|
||||
@ -399,7 +351,7 @@ export class DossierOverviewScreenComponent
|
||||
{
|
||||
key: 'assigned-to-others',
|
||||
label: this._translateService.instant('dossier-overview.quick-filters.assigned-to-others'),
|
||||
checker: (file: FileStatusWrapper) => !!file.currentReviewer && file.currentReviewer !== this.user.id
|
||||
checker: (file: FileStatusWrapper) => !!file.currentReviewer && file.currentReviewer !== this.userId
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
@ -12,8 +12,7 @@ import { AnnotationData, FileDataModel } from '@models/file/file-data.model';
|
||||
import { FileActionService } from '../../services/file-action.service';
|
||||
import { AnnotationDrawService } from '../../services/annotation-draw.service';
|
||||
import { AnnotationProcessingService } from '../../services/annotation-processing.service';
|
||||
import { tap } from 'rxjs/operators';
|
||||
import { NotificationService } from '@services/notification.service';
|
||||
import { Toaster } from '../../../../services/toaster.service';
|
||||
import { FileStatusWrapper } from '@models/file/file-status.wrapper';
|
||||
import { PermissionsService } from '@services/permissions.service';
|
||||
import { Subscription, timer } from 'rxjs';
|
||||
@ -34,9 +33,10 @@ import { DossiersDialogService } from '../../services/dossiers-dialog.service';
|
||||
import { OnAttach, OnDetach } from '@utils/custom-route-reuse.strategy';
|
||||
import { FilterModel } from '@shared/components/filters/popup-filter/model/filter.model';
|
||||
import { handleFilterDelta, processFilters } from '@shared/components/filters/popup-filter/utils/filter-utils';
|
||||
import { LoadingService } from '@services/loading.service';
|
||||
import { LoadingService } from '../../../../services/loading.service';
|
||||
import { stampPDFPage } from '../../../../utils/page-stamper';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { AutoUnsubscribeComponent } from '../../../shared/base/auto-unsubscribe.component';
|
||||
|
||||
const ALL_HOTKEY_ARRAY = ['Escape', 'F', 'f'];
|
||||
|
||||
@ -45,7 +45,7 @@ const ALL_HOTKEY_ARRAY = ['Escape', 'F', 'f'];
|
||||
templateUrl: './file-preview-screen.component.html',
|
||||
styleUrls: ['./file-preview-screen.component.scss']
|
||||
})
|
||||
export class FilePreviewScreenComponent implements OnInit, OnDestroy, OnAttach, OnDetach {
|
||||
export class FilePreviewScreenComponent extends AutoUnsubscribeComponent implements OnInit, OnDestroy, OnAttach, OnDetach {
|
||||
dialogRef: MatDialogRef<any>;
|
||||
viewMode: ViewMode = 'STANDARD';
|
||||
fullScreen = false;
|
||||
@ -59,7 +59,6 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy, OnAttach,
|
||||
secondaryFilters: FilterModel[];
|
||||
canPerformAnnotationActions: boolean;
|
||||
filesAutoUpdateTimer: Subscription;
|
||||
fileReanalysedSubscription: Subscription;
|
||||
hideSkipped = false;
|
||||
displayPDFViewer = false;
|
||||
viewDocumentInfo = false;
|
||||
@ -81,7 +80,7 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy, OnAttach,
|
||||
private readonly _activatedRoute: ActivatedRoute,
|
||||
private readonly _dialogService: DossiersDialogService,
|
||||
private readonly _router: Router,
|
||||
private readonly _notificationService: NotificationService,
|
||||
private readonly _toaster: Toaster,
|
||||
private readonly _annotationProcessingService: AnnotationProcessingService,
|
||||
private readonly _annotationDrawService: AnnotationDrawService,
|
||||
private readonly _fileActionService: FileActionService,
|
||||
@ -92,6 +91,7 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy, OnAttach,
|
||||
private readonly _loadingService: LoadingService,
|
||||
private readonly _translateService: TranslateService
|
||||
) {
|
||||
super();
|
||||
document.documentElement.addEventListener('fullscreenchange', () => {
|
||||
if (!document.fullscreenElement) {
|
||||
this.fullScreen = false;
|
||||
@ -225,7 +225,7 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy, OnAttach,
|
||||
ngOnDetach() {
|
||||
this.displayPDFViewer = false;
|
||||
this.viewReady = false;
|
||||
this._unsubscribeFromFileUpdates();
|
||||
super.ngOnDestroy();
|
||||
}
|
||||
|
||||
async ngOnAttach(previousRoute: ActivatedRouteSnapshot) {
|
||||
@ -253,10 +253,6 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy, OnAttach,
|
||||
this.viewReady = true;
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this._unsubscribeFromFileUpdates();
|
||||
}
|
||||
|
||||
rebuildFilters(deletePreviousAnnotations: boolean = false) {
|
||||
const startTime = new Date().getTime();
|
||||
if (deletePreviousAnnotations) {
|
||||
@ -473,7 +469,7 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy, OnAttach,
|
||||
await this._statusControllerService.setFileReviewer(dossierId, fileId, reviewerId).toPromise();
|
||||
|
||||
const msg = `Successfully assigned ${reviewerName} to file: ${filename}`;
|
||||
this._notificationService.showToastNotification(msg);
|
||||
this._toaster.info(msg);
|
||||
await this.appStateService.reloadActiveFile();
|
||||
this._updateCanPerformActions();
|
||||
this.editingReviewer = false;
|
||||
@ -492,7 +488,7 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy, OnAttach,
|
||||
}
|
||||
|
||||
downloadOriginalFile() {
|
||||
this._fileManagementControllerService
|
||||
this.addSubscription = this._fileManagementControllerService
|
||||
.downloadOriginalFile(this.dossierId, this.fileId, true, this.fileData.fileStatus.cacheIdentifier, 'response')
|
||||
.subscribe(data => {
|
||||
download(data, this.fileData.fileStatus.filename);
|
||||
@ -543,10 +539,8 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy, OnAttach,
|
||||
}
|
||||
|
||||
private _subscribeToFileUpdates(): void {
|
||||
this.filesAutoUpdateTimer = timer(0, 5000)
|
||||
.pipe(tap(async () => await this.appStateService.reloadActiveFile()))
|
||||
.subscribe();
|
||||
this.fileReanalysedSubscription = this.appStateService.fileReanalysed.subscribe(async (fileStatus: FileStatusWrapper) => {
|
||||
this.addSubscription = timer(0, 5000).subscribe(async () => await this.appStateService.reloadActiveFile());
|
||||
this.addSubscription = this.appStateService.fileReanalysed.subscribe(async (fileStatus: FileStatusWrapper) => {
|
||||
if (fileStatus.fileId === this.fileId) {
|
||||
await this._loadFileData(!this._reloadFileOnReanalysis);
|
||||
this._reloadFileOnReanalysis = false;
|
||||
@ -557,11 +551,6 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy, OnAttach,
|
||||
});
|
||||
}
|
||||
|
||||
private _unsubscribeFromFileUpdates(): void {
|
||||
this.filesAutoUpdateTimer.unsubscribe();
|
||||
this.fileReanalysedSubscription.unsubscribe();
|
||||
}
|
||||
|
||||
private _updateCanPerformActions() {
|
||||
this.canPerformAnnotationActions =
|
||||
this.permissionsService.canPerformAnnotationActions() &&
|
||||
|
||||
@ -1,11 +1,25 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { DossierControllerService } from '@redaction/red-ui-http';
|
||||
import { Dossier, DossierControllerService } from '@redaction/red-ui-http';
|
||||
|
||||
@Injectable()
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class DossiersService {
|
||||
constructor(private readonly _dossierControllerService: DossierControllerService) {}
|
||||
|
||||
getDeletedDossiers() {
|
||||
createOrUpdate(dossier: Dossier): Promise<Dossier> {
|
||||
return this._dossierControllerService.createOrUpdateDossier(dossier).toPromise();
|
||||
}
|
||||
|
||||
delete(dossierId: string): Promise<unknown> {
|
||||
return this._dossierControllerService.deleteDossier(dossierId).toPromise();
|
||||
}
|
||||
|
||||
getAll(): Promise<Dossier[]> {
|
||||
return this._dossierControllerService.getDossiers().toPromise();
|
||||
}
|
||||
|
||||
getDeleted(): Promise<Dossier[]> {
|
||||
return this._dossierControllerService.getDeletedDossiers().toPromise();
|
||||
}
|
||||
|
||||
|
||||
@ -7,7 +7,7 @@ import {
|
||||
ManualRedactionControllerService
|
||||
} from '@redaction/red-ui-http';
|
||||
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
||||
import { NotificationService, NotificationType } from '@services/notification.service';
|
||||
import { Toaster } from '../../../services/toaster.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { tap } from 'rxjs/operators';
|
||||
import { UserService } from '@services/user.service';
|
||||
@ -38,7 +38,7 @@ export class ManualAnnotationService {
|
||||
private readonly _appStateService: AppStateService,
|
||||
private readonly _userService: UserService,
|
||||
private readonly _translateService: TranslateService,
|
||||
private readonly _notificationService: NotificationService,
|
||||
private readonly _toaster: Toaster,
|
||||
private readonly _manualRedactionControllerService: ManualRedactionControllerService,
|
||||
private readonly _dictionaryControllerService: DictionaryControllerService,
|
||||
private readonly _permissionsService: PermissionsService
|
||||
@ -76,8 +76,12 @@ export class ManualAnnotationService {
|
||||
|
||||
return obs.pipe(
|
||||
tap(
|
||||
() => this._notify(this._getMessage(mode)),
|
||||
error => this._notify(this._getMessage(mode, modifyDictionary, true), NotificationType.ERROR, error)
|
||||
() => this._toaster.success(this._getMessage(mode), { positionClass: 'toast-file-preview' }),
|
||||
error =>
|
||||
this._toaster.error(this._getMessage(mode, modifyDictionary, true), {
|
||||
params: error,
|
||||
positionClass: 'toast-file-preview'
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
@ -223,13 +227,6 @@ export class ManualAnnotationService {
|
||||
}
|
||||
}
|
||||
|
||||
private _notify(key: string, type: NotificationType = NotificationType.SUCCESS, data?: any) {
|
||||
this._notificationService.showToastNotification(this._translateService.instant(key, data), null, type, {
|
||||
positionClass: 'toast-file-preview',
|
||||
actions: []
|
||||
});
|
||||
}
|
||||
|
||||
private _getMessage(mode: Mode, modifyDictionary?: boolean, error: boolean = false) {
|
||||
return (
|
||||
'annotation-actions.message.' +
|
||||
|
||||
@ -0,0 +1,28 @@
|
||||
import { Component, OnDestroy } from '@angular/core';
|
||||
import { Subscription } from 'rxjs';
|
||||
|
||||
/**
|
||||
* Inherit this class when you need to subscribe to observables in your components
|
||||
*/
|
||||
@Component({ template: '' })
|
||||
export abstract class AutoUnsubscribeComponent implements OnDestroy {
|
||||
private _subscriptions = new Subscription();
|
||||
|
||||
/**
|
||||
* Call this method when you want to subscribe to an observable
|
||||
* @param subscription - the new subscription to add to subscriptions array
|
||||
*/
|
||||
set addSubscription(subscription: Subscription) {
|
||||
this._subscriptions.closed = false;
|
||||
this._subscriptions.add(subscription);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method unsubscribes active subscriptions
|
||||
* If you implement OnDestroy in a component that inherits AutoUnsubscribeComponent,
|
||||
* then you must explicitly call super.ngOnDestroy()
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this._subscriptions.unsubscribe();
|
||||
}
|
||||
}
|
||||
@ -1,95 +1,97 @@
|
||||
import { Component, Injector, ViewChild } from '@angular/core';
|
||||
import { SortingOption, SortingService } from '@services/sorting.service';
|
||||
import { Component, Injector, OnDestroy, ViewChild } from '@angular/core';
|
||||
import { SortingOrders, SortingService } from '@services/sorting.service';
|
||||
import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
|
||||
import { FilterService } from '../services/filter.service';
|
||||
import { SearchService } from '../services/search.service';
|
||||
import { ScreenStateService } from '../services/screen-state.service';
|
||||
import { Observable } from 'rxjs';
|
||||
import { FilterModel } from '../components/filters/popup-filter/model/filter.model';
|
||||
import { combineLatest, Observable } from 'rxjs';
|
||||
import { AutoUnsubscribeComponent } from './auto-unsubscribe.component';
|
||||
import { distinctUntilChanged, map } from 'rxjs/operators';
|
||||
import { PermissionsService } from '../../../services/permissions.service';
|
||||
|
||||
@Component({ template: '' })
|
||||
export abstract class BaseListingComponent<T> {
|
||||
export abstract class BaseListingComponent<T> extends AutoUnsubscribeComponent implements OnDestroy {
|
||||
@ViewChild(CdkVirtualScrollViewport)
|
||||
readonly scrollViewport: CdkVirtualScrollViewport;
|
||||
|
||||
readonly filterService: FilterService<T>;
|
||||
protected readonly _sortingService: SortingService;
|
||||
protected readonly _searchService: SearchService<T>;
|
||||
protected readonly _screenStateService: ScreenStateService<T>;
|
||||
readonly permissionsService: PermissionsService;
|
||||
readonly filterService: FilterService;
|
||||
readonly sortingService: SortingService;
|
||||
readonly searchService: SearchService<T>;
|
||||
readonly screenStateService: ScreenStateService<T>;
|
||||
|
||||
readonly sortedDisplayedEntities$: Observable<T[]>;
|
||||
readonly noMatch$: Observable<boolean>;
|
||||
|
||||
/**
|
||||
* Key used in the *trackBy* function with **ngFor* or **cdkVirtualFor*
|
||||
* and in the default sorting and as the search field
|
||||
* @protected
|
||||
*/
|
||||
protected abstract _primaryKey: string;
|
||||
|
||||
protected constructor(protected readonly _injector: Injector) {
|
||||
this.filterService = this._injector.get<FilterService<T>>(FilterService);
|
||||
this._sortingService = this._injector.get<SortingService>(SortingService);
|
||||
this._searchService = this._injector.get<SearchService<T>>(SearchService);
|
||||
this._screenStateService = this._injector.get<ScreenStateService<T>>(ScreenStateService);
|
||||
super();
|
||||
this.trackByPrimaryKey = this.trackByPrimaryKey.bind(this);
|
||||
this.permissionsService = this._injector.get(PermissionsService);
|
||||
this.filterService = this._injector.get(FilterService);
|
||||
this.sortingService = this._injector.get(SortingService);
|
||||
this.searchService = this._injector.get(SearchService);
|
||||
this.screenStateService = this._injector.get<ScreenStateService<T>>(ScreenStateService);
|
||||
setTimeout(() => this.setInitialConfig());
|
||||
|
||||
this.sortedDisplayedEntities$ = this._sortedDisplayedEntities$;
|
||||
this.noMatch$ = this._noMatch$;
|
||||
}
|
||||
|
||||
get selectedEntitiesIds$(): Observable<string[]> {
|
||||
return this._screenStateService.selectedEntitiesIds$;
|
||||
setInitialConfig() {
|
||||
this.sortingService.setSortingOption({
|
||||
column: this._primaryKey,
|
||||
order: SortingOrders.ASC
|
||||
});
|
||||
this.searchService.setSearchKey(this._primaryKey);
|
||||
}
|
||||
|
||||
get displayedEntities$(): Observable<T[]> {
|
||||
return this._screenStateService.displayedEntities$;
|
||||
ngOnDestroy(): void {
|
||||
super.ngOnDestroy();
|
||||
}
|
||||
|
||||
get allEntities$(): Observable<T[]> {
|
||||
return this._screenStateService.entities$;
|
||||
}
|
||||
|
||||
get displayedEntities(): T[] {
|
||||
return this._screenStateService.displayedEntities;
|
||||
private get _sortedDisplayedEntities$(): Observable<T[]> {
|
||||
return this.screenStateService.displayedEntities$.pipe(map(entities => this.sortingService.defaultSort(entities)));
|
||||
}
|
||||
|
||||
get allEntities(): T[] {
|
||||
return this._screenStateService.entities;
|
||||
return this.screenStateService.allEntities;
|
||||
}
|
||||
|
||||
get areAllEntitiesSelected() {
|
||||
return this._screenStateService.areAllEntitiesSelected;
|
||||
private get _noMatch$(): Observable<boolean> {
|
||||
return combineLatest([this.screenStateService.allEntitiesLength$, this.screenStateService.displayedLength$]).pipe(
|
||||
map(([hasEntities, hasDisplayedEntities]) => hasEntities && !hasDisplayedEntities),
|
||||
distinctUntilChanged()
|
||||
);
|
||||
}
|
||||
|
||||
get areSomeEntitiesSelected$() {
|
||||
return this._screenStateService.areSomeEntitiesSelected$;
|
||||
}
|
||||
|
||||
get sortingOption(): SortingOption {
|
||||
return this._sortingService.getSortingOption();
|
||||
}
|
||||
|
||||
get searchForm() {
|
||||
return this._searchService.searchForm;
|
||||
}
|
||||
|
||||
get noMatch(): boolean {
|
||||
return this.allEntities.length && this.displayedEntities?.length === 0;
|
||||
}
|
||||
|
||||
get noData(): boolean {
|
||||
return this.allEntities.length === 0;
|
||||
}
|
||||
|
||||
getFilter$(slug: string): Observable<FilterModel[]> {
|
||||
return this.filterService.getFilter$(slug);
|
||||
}
|
||||
|
||||
resetFilters() {
|
||||
this.filterService.reset();
|
||||
}
|
||||
|
||||
toggleSort($event) {
|
||||
this._sortingService.toggleSort($event);
|
||||
canBulkDelete$(hasPermission = true): Observable<boolean> {
|
||||
return this.screenStateService.areSomeEntitiesSelected$.pipe(
|
||||
map(areSomeEntitiesSelected => areSomeEntitiesSelected && hasPermission),
|
||||
distinctUntilChanged()
|
||||
);
|
||||
}
|
||||
|
||||
toggleSelectAll() {
|
||||
return this._screenStateService.toggleSelectAll();
|
||||
return this.screenStateService.selectEntities();
|
||||
}
|
||||
|
||||
toggleEntitySelected(event: MouseEvent, entity: T) {
|
||||
event.stopPropagation();
|
||||
return this._screenStateService.toggleEntitySelected(entity);
|
||||
return this.screenStateService.selectEntities([entity]);
|
||||
}
|
||||
|
||||
isSelected(entity: T) {
|
||||
return this._screenStateService.isSelected(entity);
|
||||
isSelected(entity: T): boolean {
|
||||
return this.screenStateService.isSelected(entity);
|
||||
}
|
||||
|
||||
trackByPrimaryKey(index: number, item: T) {
|
||||
return item[this._primaryKey];
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,11 +1,12 @@
|
||||
import { ChangeDetectionStrategy, Component, Inject, Input } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, Component, Inject, Input, OnDestroy } from '@angular/core';
|
||||
import { PermissionsService } from '@services/permissions.service';
|
||||
import { DossierWrapper } from '@state/model/dossier.wrapper';
|
||||
import { FileStatusWrapper } from '@models/file/file-status.wrapper';
|
||||
import { FileDownloadService } from '@upload-download/services/file-download.service';
|
||||
import { NotificationService } from '@services/notification.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Toaster } from '@services/toaster.service';
|
||||
import { BASE_HREF } from '../../../../../tokens';
|
||||
import { AutoUnsubscribeComponent } from '@shared/base/auto-unsubscribe.component';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
export type MenuState = 'OPEN' | 'CLOSED';
|
||||
|
||||
@ -15,7 +16,7 @@ export type MenuState = 'OPEN' | 'CLOSED';
|
||||
styleUrls: ['./file-download-btn.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class FileDownloadBtnComponent {
|
||||
export class FileDownloadBtnComponent extends AutoUnsubscribeComponent implements OnDestroy {
|
||||
@Input() dossier: DossierWrapper;
|
||||
@Input() file: FileStatusWrapper | FileStatusWrapper[];
|
||||
@Input() tooltipPosition: 'above' | 'below' | 'before' | 'after' = 'above';
|
||||
@ -27,9 +28,11 @@ export class FileDownloadBtnComponent {
|
||||
@Inject(BASE_HREF) private readonly _baseHref: string,
|
||||
private readonly _permissionsService: PermissionsService,
|
||||
private readonly _fileDownloadService: FileDownloadService,
|
||||
private readonly _translateService: TranslateService,
|
||||
private readonly _notificationService: NotificationService
|
||||
) {}
|
||||
private readonly _toaster: Toaster,
|
||||
private readonly _translateService: TranslateService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
get canDownloadFiles() {
|
||||
if (!Array.isArray(this.file)) {
|
||||
@ -47,12 +50,10 @@ export class FileDownloadBtnComponent {
|
||||
|
||||
downloadFiles($event: MouseEvent) {
|
||||
$event.stopPropagation();
|
||||
this._fileDownloadService.downloadFiles(Array.isArray(this.file) ? this.file : [this.file], this.dossier).subscribe(() => {
|
||||
this._notificationService.showToastNotification(
|
||||
this._translateService.instant('download-status.queued', {
|
||||
baseUrl: this._baseHref
|
||||
})
|
||||
);
|
||||
});
|
||||
this.addSubscription = this._fileDownloadService
|
||||
.downloadFiles(Array.isArray(this.file) ? this.file : [this.file], this.dossier)
|
||||
.subscribe(() => {
|
||||
this._toaster.info('download-status.queued', { params: { baseUrl: this._baseHref } });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-empty-state',
|
||||
templateUrl: './empty-state.component.html',
|
||||
styleUrls: ['./empty-state.component.scss']
|
||||
styleUrls: ['./empty-state.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class EmptyStateComponent implements OnInit {
|
||||
@Input() text: string;
|
||||
@ -15,8 +16,6 @@ export class EmptyStateComponent implements OnInit {
|
||||
@Input() verticalPadding = 120;
|
||||
@Output() action = new EventEmitter();
|
||||
|
||||
constructor() {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.showButton = this.showButton && this.action.observers.length > 0;
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { FilterModel } from './filter.model';
|
||||
import { TemplateRef } from '@angular/core';
|
||||
|
||||
export interface FilterWrapper {
|
||||
export interface FilterGroup {
|
||||
slug: string;
|
||||
label?: string;
|
||||
icon?: string;
|
||||
|
||||
@ -2,7 +2,7 @@ import { FilterModel } from '../model/filter.model';
|
||||
import { FileStatusWrapper } from '@models/file/file-status.wrapper';
|
||||
import { DossierWrapper } from '@state/model/dossier.wrapper';
|
||||
import { PermissionsService } from '@services/permissions.service';
|
||||
import { FilterWrapper } from '@shared/components/filters/popup-filter/model/filter-wrapper.model';
|
||||
import { FilterGroup } from '@shared/components/filters/popup-filter/model/filter-wrapper.model';
|
||||
|
||||
export function processFilters(oldFilters: FilterModel[], newFilters: FilterModel[]) {
|
||||
copySettings(oldFilters, newFilters);
|
||||
@ -159,7 +159,7 @@ export const addedDateChecker = (dw: DossierWrapper, filter: FilterModel) => dw.
|
||||
|
||||
export const dossierApproverChecker = (dw: DossierWrapper, filter: FilterModel) => dw.approverIds.includes(filter.key);
|
||||
|
||||
export function getFilteredEntities<T>(entities: T[], filters: FilterWrapper[]) {
|
||||
export function getFilteredEntities<T>(entities: T[], filters: FilterGroup[]) {
|
||||
const filteredEntities: T[] = [];
|
||||
for (const entity of entities) {
|
||||
let add = true;
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<div
|
||||
(click)="filterService.toggleFilter('quickFilters', filter.key)"
|
||||
*ngFor="let filter of filterService.getFilter$('quickFilters') | async"
|
||||
*ngFor="let filter of quickFilters$ | async"
|
||||
[class.active]="filter.checked"
|
||||
class="quick-filter"
|
||||
>
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
import { Component, EventEmitter, Output } from '@angular/core';
|
||||
import { FilterModel } from '../popup-filter/model/filter.model';
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||
import { FilterService } from '@shared/services/filter.service';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-quick-filters',
|
||||
templateUrl: './quick-filters.component.html',
|
||||
styleUrls: ['./quick-filters.component.scss']
|
||||
styleUrls: ['./quick-filters.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class QuickFiltersComponent<T> {
|
||||
@Output() filtersChanged = new EventEmitter<FilterModel[]>();
|
||||
export class QuickFiltersComponent {
|
||||
readonly quickFilters$ = this.filterService.getFilterModels$('quickFilters');
|
||||
|
||||
constructor(readonly filterService: FilterService<T>) {}
|
||||
constructor(readonly filterService: FilterService) {}
|
||||
}
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
|
||||
<ng-container *ngFor="let config of filters; trackBy: trackByLabel">
|
||||
<redaction-popup-filter
|
||||
(filtersChanged)="filterService.filterEntities()"
|
||||
(filtersChanged)="filterService.refresh()"
|
||||
*ngIf="!config.hide"
|
||||
[filterLabel]="config.label"
|
||||
[filterTemplate]="config.filterTemplate"
|
||||
@ -16,31 +16,26 @@
|
||||
</ng-container>
|
||||
|
||||
<redaction-input-with-action
|
||||
*ngIf="searchService.isSearchNeeded"
|
||||
*ngIf="searchPlaceholder"
|
||||
[form]="searchService.searchForm"
|
||||
[placeholder]="searchPlaceholder"
|
||||
type="search"
|
||||
></redaction-input-with-action>
|
||||
|
||||
<div
|
||||
(click)="resetFilters()"
|
||||
*ngIf="(filterService.showResetFilters$ | async) || searchService.searchValue"
|
||||
class="reset-filters"
|
||||
translate="reset-filters"
|
||||
></div>
|
||||
<div (click)="resetFilters()" *ngIf="showResetFilters$ | async" class="reset-filters" translate="reset-filters"></div>
|
||||
</div>
|
||||
|
||||
<ng-container *ngFor="let config of buttonConfigs; trackBy: trackByLabel">
|
||||
<redaction-icon-button
|
||||
(action)="config.action($event)"
|
||||
*ngIf="!config.hide"
|
||||
[icon]="config.icon"
|
||||
[label]="config.label"
|
||||
[type]="config.type"
|
||||
></redaction-icon-button>
|
||||
</ng-container>
|
||||
<div class="actions" *ngIf="showCloseButton || actionConfigs || buttonConfigs">
|
||||
<ng-container *ngFor="let config of buttonConfigs; trackBy: trackByLabel">
|
||||
<redaction-icon-button
|
||||
(action)="config.action($event)"
|
||||
*ngIf="!config.hide"
|
||||
[icon]="config.icon"
|
||||
[label]="config.label"
|
||||
[type]="config.type"
|
||||
></redaction-icon-button>
|
||||
</ng-container>
|
||||
|
||||
<div *ngIf="showCloseButton || actionConfigs" class="actions">
|
||||
<ng-container *ngFor="let config of actionConfigs; trackBy: trackByLabel">
|
||||
<redaction-circle-button
|
||||
(action)="config.action($event)"
|
||||
@ -55,9 +50,9 @@
|
||||
<ng-content></ng-content>
|
||||
|
||||
<redaction-circle-button
|
||||
*ngIf="showCloseButton && permissionsService.isUser()"
|
||||
[class.ml-6]="actionConfigs"
|
||||
*ngIf="showCloseButton"
|
||||
[tooltip]="'common.close' | translate"
|
||||
[class.ml-6]="actionConfigs"
|
||||
icon="red:close"
|
||||
redactionNavigateLastDossiersScreen
|
||||
tooltipPosition="below"
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { PermissionsService } from '@services/permissions.service';
|
||||
import { Component, Input, Optional } from '@angular/core';
|
||||
import { ActionConfig } from '@shared/components/page-header/models/action-config.model';
|
||||
import { ButtonConfig } from '@shared/components/page-header/models/button-config.model';
|
||||
import { FilterService } from '@shared/services/filter.service';
|
||||
import { SearchService } from '@shared/services/search.service';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { distinctUntilChanged, map } from 'rxjs/operators';
|
||||
import { combineLatest, Observable, of } from 'rxjs';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-page-header',
|
||||
@ -18,22 +18,30 @@ export class PageHeaderComponent<T> {
|
||||
@Input() buttonConfigs: ButtonConfig[];
|
||||
@Input() searchPlaceholder: string;
|
||||
|
||||
constructor(
|
||||
readonly permissionsService: PermissionsService,
|
||||
readonly filterService: FilterService<T>,
|
||||
readonly searchService: SearchService<T>
|
||||
) {}
|
||||
readonly filters$ = this.filterService?.filterGroups$.pipe(map(all => all.filter(f => f.icon)));
|
||||
readonly showResetFilters$ = this._showResetFilters$;
|
||||
|
||||
get filters$() {
|
||||
return this.filterService.allFilters$.pipe(map(all => all.filter(f => f.icon)));
|
||||
constructor(@Optional() readonly filterService: FilterService, @Optional() readonly searchService: SearchService<T>) {}
|
||||
|
||||
get _showResetFilters$(): Observable<boolean> {
|
||||
if (!this.filterService) return of(false);
|
||||
|
||||
const filtersLength$ = this.filters$.pipe(
|
||||
map(f => f.length),
|
||||
distinctUntilChanged()
|
||||
);
|
||||
return combineLatest([filtersLength$, this.filterService.showResetFilters$, this.searchService.valueChanges$]).pipe(
|
||||
map(([hasFilters, showResetFilters, searchValue]) => hasFilters && (showResetFilters || !!searchValue)),
|
||||
distinctUntilChanged()
|
||||
);
|
||||
}
|
||||
|
||||
resetFilters() {
|
||||
resetFilters(): void {
|
||||
this.filterService.reset();
|
||||
this.searchService.reset();
|
||||
}
|
||||
|
||||
trackByLabel(index: number, item) {
|
||||
trackByLabel<K extends { label?: string }>(index: number, item: K): string {
|
||||
return item.label;
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
<svg [attr.height]="size" [attr.width]="size" [style.min-width]="size" attr.viewBox="0 0 {{ size }} {{ size }}" class="donut-chart">
|
||||
<g *ngFor="let value of config; let i = index">
|
||||
<circle
|
||||
*ngIf="exists(i)"
|
||||
*ngIf="!!chartData[i]"
|
||||
[attr.cx]="cx"
|
||||
[attr.cy]="cy"
|
||||
[attr.r]="radius"
|
||||
@ -27,8 +27,8 @@
|
||||
<div
|
||||
(click)="selectValue(val.key)"
|
||||
*ngFor="let val of config"
|
||||
[class.active]="filterService.filterChecked$('statusFilters', val.key) | async"
|
||||
[class.filter-disabled]="!filter"
|
||||
[class.active]="filterChecked$(val.key) | async"
|
||||
[class.filter-disabled]="(statusFilters$ | async)?.length === 0"
|
||||
>
|
||||
<redaction-status-bar
|
||||
[config]="[
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import { Component, EventEmitter, Input, OnChanges, Output } from '@angular/core';
|
||||
import { Component, Input, OnChanges } from '@angular/core';
|
||||
import { Color } from '@utils/types';
|
||||
import { FilterModel } from '@shared/components/filters/popup-filter/model/filter.model';
|
||||
import { FilterService } from '@shared/services/filter.service';
|
||||
import { Observable, of } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
export interface DoughnutChartConfig {
|
||||
value: number;
|
||||
@ -16,17 +17,14 @@ export interface DoughnutChartConfig {
|
||||
templateUrl: './simple-doughnut-chart.component.html',
|
||||
styleUrls: ['./simple-doughnut-chart.component.scss']
|
||||
})
|
||||
export class SimpleDoughnutChartComponent<T> implements OnChanges {
|
||||
export class SimpleDoughnutChartComponent implements OnChanges {
|
||||
@Input() subtitle: string;
|
||||
@Input() config: DoughnutChartConfig[] = [];
|
||||
@Input() radius = 85;
|
||||
@Input() strokeWidth = 20;
|
||||
@Input() direction: 'row' | 'column' = 'column';
|
||||
@Input() filter: FilterModel[];
|
||||
@Input() totalType: 'sum' | 'count' = 'sum';
|
||||
@Input() counterText: string;
|
||||
@Output()
|
||||
toggleFilter = new EventEmitter<string>();
|
||||
|
||||
chartData: any[] = [];
|
||||
perimeter: number;
|
||||
@ -34,13 +32,15 @@ export class SimpleDoughnutChartComponent<T> implements OnChanges {
|
||||
cy = 0;
|
||||
size = 0;
|
||||
|
||||
constructor(readonly filterService: FilterService<T>) {}
|
||||
readonly statusFilters$ = this.filterService.getFilterModels$('statusFilters') ?? of([]);
|
||||
|
||||
get circumference() {
|
||||
constructor(readonly filterService: FilterService) {}
|
||||
|
||||
get circumference(): number {
|
||||
return 2 * Math.PI * this.radius;
|
||||
}
|
||||
|
||||
get dataTotal() {
|
||||
get dataTotal(): number {
|
||||
return this.config.map(v => v.value).reduce((acc, val) => acc + val, 0);
|
||||
}
|
||||
|
||||
@ -55,47 +55,39 @@ export class SimpleDoughnutChartComponent<T> implements OnChanges {
|
||||
this.size = this.strokeWidth + this.radius * 2;
|
||||
}
|
||||
|
||||
calculateChartData() {
|
||||
const newData = [];
|
||||
let angleOffset = -90;
|
||||
this.config.forEach(dataVal => {
|
||||
if (dataVal.value > 0) {
|
||||
const data = {
|
||||
degrees: angleOffset
|
||||
};
|
||||
newData.push(data);
|
||||
angleOffset = this.dataPercentage(dataVal.value) * 360 + angleOffset;
|
||||
} else {
|
||||
newData.push(null);
|
||||
}
|
||||
});
|
||||
this.chartData = newData;
|
||||
filterChecked$(key: string): Observable<boolean> {
|
||||
return this.statusFilters$.pipe(map(all => all?.find(e => e.key === key)?.checked));
|
||||
}
|
||||
|
||||
calculateStrokeDashOffset(dataVal) {
|
||||
calculateChartData() {
|
||||
let angleOffset = -90;
|
||||
this.chartData = this.config.map(dataVal => {
|
||||
if (dataVal.value === 0) return null;
|
||||
|
||||
const res = { degrees: angleOffset };
|
||||
angleOffset = this.dataPercentage(dataVal.value) * 360 + angleOffset;
|
||||
return res;
|
||||
});
|
||||
}
|
||||
|
||||
calculateStrokeDashOffset(dataVal: number): number {
|
||||
const strokeDiff = this.dataPercentage(dataVal) * this.circumference;
|
||||
return this.circumference - strokeDiff;
|
||||
}
|
||||
|
||||
dataPercentage(dataVal) {
|
||||
dataPercentage(dataVal: number): number {
|
||||
return dataVal / this.dataTotal;
|
||||
}
|
||||
|
||||
returnCircleTransformValue(index) {
|
||||
returnCircleTransformValue(index: number) {
|
||||
return `rotate(${this.chartData[index].degrees}, ${this.cx}, ${this.cy})`;
|
||||
}
|
||||
|
||||
getLabel(config: DoughnutChartConfig): string {
|
||||
return this.totalType === 'sum' ? `${config.value} ${config.label}` : `${config.label} (${config.value} ${this.counterText})`;
|
||||
getLabel({ label, value }: DoughnutChartConfig): string {
|
||||
return this.totalType === 'sum' ? `${value} ${label}` : `${label} (${value} ${this.counterText})`;
|
||||
}
|
||||
|
||||
selectValue(key: string) {
|
||||
selectValue(key: string): void {
|
||||
this.filterService.toggleFilter('statusFilters', key);
|
||||
this.filterService.filterEntities();
|
||||
this.toggleFilter.emit(key);
|
||||
}
|
||||
|
||||
exists(index: number) {
|
||||
return !!this.chartData[index];
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,23 +0,0 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
import { orderBy } from 'lodash';
|
||||
import { SortingOrders } from '@services/sorting.service';
|
||||
|
||||
@Pipe({ name: 'sortBy' })
|
||||
export class SortByPipe implements PipeTransform {
|
||||
transform<T>(value: T[], order = '', column: string = ''): T[] {
|
||||
if (!value || order === '' || !order) {
|
||||
return value;
|
||||
} // no array
|
||||
if (!column || column === '') {
|
||||
if (order === SortingOrders.ASC) {
|
||||
return value.sort();
|
||||
} else {
|
||||
return value.sort().reverse();
|
||||
}
|
||||
} // sort 1d array
|
||||
if (value.length <= 1) {
|
||||
return value;
|
||||
} // array with only one item
|
||||
return orderBy(value, [column], [order]);
|
||||
}
|
||||
}
|
||||
@ -1,9 +1,13 @@
|
||||
<div (click)="withSort && toggleSort.emit(column)" [class.pointer]="withSort" [ngClass]="class">
|
||||
<div (click)="withSort && sortingService?.toggleSort(column)" [class.pointer]="withSort" [ngClass]="class">
|
||||
<mat-icon *ngIf="!!leftIcon" [svgIcon]="leftIcon"></mat-icon>
|
||||
<span class="all-caps-label">{{ label }}</span>
|
||||
<mat-icon *ngIf="!!rightIcon" [matTooltip]="rightIconTooltip" [svgIcon]="rightIcon" matTooltipPosition="above"></mat-icon>
|
||||
<div *ngIf="withSort" [class.force-display]="activeSortingOption.column === column" class="sort-arrows-container">
|
||||
<mat-icon *ngIf="activeSortingOption?.order === 'asc'" svgIcon="red:sort-asc"></mat-icon>
|
||||
<mat-icon *ngIf="activeSortingOption?.order === 'desc'" svgIcon="red:sort-desc"></mat-icon>
|
||||
<div
|
||||
*ngIf="withSort && sortingService"
|
||||
[class.force-display]="sortingService.sortingOption?.column === column"
|
||||
class="sort-arrows-container"
|
||||
>
|
||||
<mat-icon *ngIf="sortingService.sortingOption?.order === 'asc'" svgIcon="red:sort-asc"></mat-icon>
|
||||
<mat-icon *ngIf="sortingService.sortingOption?.order === 'desc'" svgIcon="red:sort-desc"></mat-icon>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,5 +1,15 @@
|
||||
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
import { SortingOption } from '@services/sorting.service';
|
||||
import { Component, Input, Optional } from '@angular/core';
|
||||
import { SortingService } from '@services/sorting.service';
|
||||
|
||||
export interface TableColConfig {
|
||||
readonly column?: string;
|
||||
readonly label: string;
|
||||
readonly withSort?: boolean;
|
||||
readonly class?: string;
|
||||
readonly leftIcon?: string;
|
||||
readonly rightIcon?: string;
|
||||
readonly rightIconTooltip?: string;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-table-col-name',
|
||||
@ -7,7 +17,6 @@ import { SortingOption } from '@services/sorting.service';
|
||||
styleUrls: ['./table-col-name.component.scss']
|
||||
})
|
||||
export class TableColNameComponent {
|
||||
@Input() activeSortingOption: SortingOption;
|
||||
@Input() column: string;
|
||||
@Input() label: string;
|
||||
@Input() withSort = false;
|
||||
@ -16,5 +25,5 @@ export class TableColNameComponent {
|
||||
@Input() rightIcon: string;
|
||||
@Input() rightIconTooltip: string;
|
||||
|
||||
@Output() toggleSort = new EventEmitter<string>();
|
||||
constructor(@Optional() readonly sortingService: SortingService) {}
|
||||
}
|
||||
|
||||
@ -0,0 +1,22 @@
|
||||
<div class="header-item">
|
||||
<span class="all-caps-label">
|
||||
{{ tableHeaderLabel | translate: { length: (screenStateService.displayedLength$ | async) } }}
|
||||
</span>
|
||||
|
||||
<redaction-quick-filters></redaction-quick-filters>
|
||||
</div>
|
||||
|
||||
<div class="table-header" redactionSyncWidth="table-item">
|
||||
<redaction-table-col-name
|
||||
*ngFor="let config of tableColConfigs"
|
||||
[withSort]="config.withSort"
|
||||
[column]="config.column"
|
||||
[label]="config.label"
|
||||
[class]="config.class"
|
||||
[leftIcon]="config.leftIcon"
|
||||
[rightIcon]="config.rightIcon"
|
||||
[rightIconTooltip]="config.rightIconTooltip"
|
||||
></redaction-table-col-name>
|
||||
|
||||
<div class="scrollbar-placeholder"></div>
|
||||
</div>
|
||||
@ -0,0 +1,16 @@
|
||||
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
|
||||
import { TableColConfig } from '@shared/components/table-col-name/table-col-name.component';
|
||||
import { ScreenStateService } from '@shared/services/screen-state.service';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-table-header',
|
||||
templateUrl: './table-header.component.html',
|
||||
styleUrls: ['./table-header.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class TableHeaderComponent<T> {
|
||||
@Input() tableHeaderLabel: string;
|
||||
@Input() tableColConfigs: TableColConfig[];
|
||||
|
||||
constructor(readonly screenStateService: ScreenStateService<T>) {}
|
||||
}
|
||||
@ -25,6 +25,7 @@ export class SyncWidthDirective implements AfterViewInit, OnDestroy {
|
||||
@debounce(10)
|
||||
matchWidth() {
|
||||
const headerItems = this._elementRef.nativeElement.children;
|
||||
// const tableRows = document.getElementsByClassName(this.redactionSyncWidth);
|
||||
const tableRows = this._elementRef.nativeElement.parentElement.getElementsByClassName(this.redactionSyncWidth);
|
||||
|
||||
if (!tableRows || !tableRows.length) {
|
||||
|
||||
11
apps/red-ui/src/app/modules/shared/pipes/sort-by.pipe.ts
Normal file
11
apps/red-ui/src/app/modules/shared/pipes/sort-by.pipe.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
import { SortingService } from '../../../services/sorting.service';
|
||||
|
||||
@Pipe({ name: 'sortBy' })
|
||||
export class SortByPipe implements PipeTransform {
|
||||
constructor(private readonly _sortingService: SortingService) {}
|
||||
|
||||
transform<T>(value: T[], order = '', column: string = ''): T[] {
|
||||
return this._sortingService.sort(value, order, column);
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,6 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Observable, throwError } from 'rxjs';
|
||||
import { NotificationService, NotificationType } from '@services/notification.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Toaster } from '../../../services/toaster.service';
|
||||
import { DictionaryControllerService } from '@redaction/red-ui-http';
|
||||
import { tap } from 'rxjs/operators';
|
||||
|
||||
@ -11,11 +10,7 @@ const MIN_WORD_LENGTH = 2;
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class DictionarySaveService {
|
||||
constructor(
|
||||
private readonly _notificationService: NotificationService,
|
||||
private readonly _translateService: TranslateService,
|
||||
private readonly _dictionaryControllerService: DictionaryControllerService
|
||||
) {}
|
||||
constructor(private readonly _toaster: Toaster, private readonly _dictionaryControllerService: DictionaryControllerService) {}
|
||||
|
||||
saveEntries(
|
||||
entries: string[],
|
||||
@ -44,29 +39,13 @@ export class DictionarySaveService {
|
||||
return obs.pipe(
|
||||
tap(
|
||||
() => {
|
||||
if (showToast) {
|
||||
this._notificationService.showToastNotification(
|
||||
this._translateService.instant('dictionary-overview.success.generic'),
|
||||
null,
|
||||
NotificationType.SUCCESS
|
||||
);
|
||||
}
|
||||
if (showToast) this._toaster.success('dictionary-overview.success.generic');
|
||||
},
|
||||
() => {
|
||||
this._notificationService.showToastNotification(
|
||||
this._translateService.instant('dictionary-overview.error.generic'),
|
||||
null,
|
||||
NotificationType.ERROR
|
||||
);
|
||||
}
|
||||
() => this._toaster.error('dictionary-overview.error.generic')
|
||||
)
|
||||
);
|
||||
} else {
|
||||
this._notificationService.showToastNotification(
|
||||
this._translateService.instant('dictionary-overview.error.entries-too-short'),
|
||||
null,
|
||||
NotificationType.ERROR
|
||||
);
|
||||
this._toaster.error('dictionary-overview.error.entries-too-short');
|
||||
|
||||
return throwError('Entries too short');
|
||||
}
|
||||
|
||||
@ -1,86 +1,59 @@
|
||||
import { ChangeDetectorRef, Injectable } from '@angular/core';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { FilterModel } from '@shared/components/filters/popup-filter/model/filter.model';
|
||||
import { getFilteredEntities, processFilters } from '@shared/components/filters/popup-filter/utils/filter-utils';
|
||||
import { FilterWrapper } from '@shared/components/filters/popup-filter/model/filter-wrapper.model';
|
||||
import { ScreenStateService } from '@shared/services/screen-state.service';
|
||||
import { SearchService } from '@shared/services/search.service';
|
||||
import { FilterGroup } from '@shared/components/filters/popup-filter/model/filter-wrapper.model';
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
import { distinctUntilChanged, filter, map } from 'rxjs/operators';
|
||||
import { distinctUntilChanged, map, switchMap } from 'rxjs/operators';
|
||||
|
||||
@Injectable()
|
||||
export class FilterService<T> {
|
||||
_allFilters$ = new BehaviorSubject<FilterWrapper[]>([]);
|
||||
export class FilterService {
|
||||
private readonly _filterGroups$ = new BehaviorSubject<FilterGroup[]>([]);
|
||||
private readonly _refresh$ = new BehaviorSubject(null);
|
||||
|
||||
constructor(
|
||||
private readonly _screenStateService: ScreenStateService<T>,
|
||||
private readonly _searchService: SearchService<T>,
|
||||
private readonly _changeDetector: ChangeDetectorRef
|
||||
) {}
|
||||
readonly filterGroups$ = this._refresh$.pipe(switchMap(() => this._filterGroups$.asObservable()));
|
||||
readonly showResetFilters$ = this._showResetFilters$;
|
||||
|
||||
get filters() {
|
||||
return Object.values(this._allFilters$.getValue());
|
||||
get filterGroups(): FilterGroup[] {
|
||||
return Object.values(this._filterGroups$.getValue());
|
||||
}
|
||||
|
||||
get showResetFilters$() {
|
||||
return this.allFilters$.pipe(
|
||||
map(all => this._toFlatFilters(all)),
|
||||
filter(f => !!f.find(el => el.checked)),
|
||||
distinctUntilChanged()
|
||||
);
|
||||
}
|
||||
|
||||
filterChecked$(slug: string, key: string) {
|
||||
const filters = this.getFilter$(slug);
|
||||
return filters.pipe(map(all => all.find(f => f.key === key)?.checked));
|
||||
refresh(): void {
|
||||
this._refresh$.next(null);
|
||||
}
|
||||
|
||||
toggleFilter(slug: string, key: string) {
|
||||
const filters = this.filters.find(f => f.slug === slug);
|
||||
let found = filters.values.find(f => f.key === key);
|
||||
if (!found) found = filters.values.map(f => f.filters?.find(ff => ff.key === key))[0];
|
||||
const filters = this.filterGroups.find(f => f.slug === slug).values;
|
||||
let found = filters.find(f => f.key === key);
|
||||
if (!found) found = filters.map(f => f.filters?.find(ff => ff.key === key))[0];
|
||||
found.checked = !found.checked;
|
||||
|
||||
this._allFilters$.next(this.filters);
|
||||
this.filterEntities();
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
filterEntities(): void {
|
||||
const filtered = getFilteredEntities(this._screenStateService.entities, this.filters);
|
||||
this._screenStateService.setFilteredEntities(filtered);
|
||||
this._searchService.executeSearchImmediately();
|
||||
|
||||
this._changeDetector.detectChanges();
|
||||
}
|
||||
|
||||
addFilter(value: FilterWrapper): void {
|
||||
const oldFilters = this.getFilter(value.slug)?.values;
|
||||
if (!oldFilters) return this._allFilters$.next([...this.filters, value]);
|
||||
addFilterGroup(value: FilterGroup): void {
|
||||
const oldFilters = this.getFilterGroup(value.slug)?.values;
|
||||
if (!oldFilters) return this._filterGroups$.next([...this.filterGroups, value]);
|
||||
|
||||
value.values = processFilters(oldFilters, value.values);
|
||||
this._allFilters$.next([...this.filters.filter(f => f.slug !== value.slug), value]);
|
||||
this._filterGroups$.next([...this.filterGroups.filter(f => f.slug !== value.slug), value]);
|
||||
}
|
||||
|
||||
getFilter(slug: string): FilterWrapper {
|
||||
return this.filters.find(f => f?.slug === slug);
|
||||
getFilterGroup(slug: string): FilterGroup {
|
||||
return this.filterGroups.find(f => f.slug === slug);
|
||||
}
|
||||
|
||||
getFilter$(slug: string): Observable<FilterModel[]> {
|
||||
return this.getFilterWrapper$(slug).pipe(
|
||||
filter(f => f !== null && f !== undefined),
|
||||
map(f => f?.values)
|
||||
);
|
||||
getFilterModels$(filterGroupSlug: string): Observable<FilterModel[]> {
|
||||
return this.getFilterGroup$(filterGroupSlug).pipe(map(f => f?.values));
|
||||
}
|
||||
|
||||
getFilterWrapper$(slug: string): Observable<FilterWrapper> {
|
||||
return this.allFilters$.pipe(map(all => all.find(f => f?.slug === slug)));
|
||||
}
|
||||
|
||||
get allFilters$(): Observable<FilterWrapper[]> {
|
||||
return this._allFilters$.asObservable();
|
||||
getFilterGroup$(slug: string): Observable<FilterGroup> {
|
||||
return this.filterGroups$.pipe(map(all => all.find(f => f.slug === slug)));
|
||||
}
|
||||
|
||||
reset(): void {
|
||||
this.filters.forEach(item => {
|
||||
this.filterGroups.forEach(item => {
|
||||
item.values.forEach(child => {
|
||||
child.checked = false;
|
||||
child.indeterminate = false;
|
||||
@ -90,11 +63,19 @@ export class FilterService<T> {
|
||||
});
|
||||
});
|
||||
});
|
||||
this._allFilters$.next(this.filters);
|
||||
this.filterEntities();
|
||||
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
private _toFlatFilters(entities: FilterWrapper[]): FilterModel[] {
|
||||
private get _showResetFilters$(): Observable<boolean> {
|
||||
return this.filterGroups$.pipe(
|
||||
map(all => this._toFlatFilters(all)),
|
||||
map(f => !!f.find(el => el.checked)),
|
||||
distinctUntilChanged()
|
||||
);
|
||||
}
|
||||
|
||||
private _toFlatFilters(entities: FilterGroup[]): FilterModel[] {
|
||||
const flatChildren = (filters: FilterModel[]) => (filters ?? []).reduce((acc, f) => [...acc, ...(f?.filters ?? [])], []);
|
||||
|
||||
return entities.reduce((acc, f) => [...acc, ...f.values, ...flatChildren(f.values)], []);
|
||||
|
||||
@ -1,107 +1,152 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
import { distinctUntilChanged, map } from 'rxjs/operators';
|
||||
import { BehaviorSubject, combineLatest, Observable } from 'rxjs';
|
||||
import { distinctUntilChanged, map, tap } from 'rxjs/operators';
|
||||
import { FilterService } from '@shared/services/filter.service';
|
||||
import { SearchService } from '@shared/services/search.service';
|
||||
import { getFilteredEntities } from '@shared/components/filters/popup-filter/utils/filter-utils';
|
||||
|
||||
const toLengthValue = entities => entities?.length ?? 0;
|
||||
|
||||
@Injectable()
|
||||
export class ScreenStateService<T> {
|
||||
entities$ = new BehaviorSubject<T[]>([]);
|
||||
filteredEntities$ = new BehaviorSubject<T[]>([]);
|
||||
displayedEntities$ = new BehaviorSubject<T[]>([]);
|
||||
selectedEntitiesIds$ = new BehaviorSubject<string[]>([]);
|
||||
private readonly _allEntities$ = new BehaviorSubject<T[]>([]);
|
||||
readonly allEntities$ = this._allEntities$.asObservable();
|
||||
readonly allEntitiesLength$ = this._allEntitiesLength$;
|
||||
|
||||
private _idKey: string;
|
||||
private readonly _displayedEntities$ = new BehaviorSubject<T[]>([]);
|
||||
readonly displayedEntities$ = this._getDisplayedEntities$;
|
||||
readonly displayedLength$ = this._displayedLength$;
|
||||
|
||||
get entities(): T[] {
|
||||
return Object.values(this.entities$.getValue());
|
||||
private readonly _selectedEntities$ = new BehaviorSubject<T[]>([]);
|
||||
readonly selectedEntities$ = this._selectedEntities$.asObservable();
|
||||
readonly selectedLength$ = this._selectedLength$;
|
||||
|
||||
readonly noData$ = this._noData$;
|
||||
readonly areAllEntitiesSelected$ = this._areAllEntitiesSelected$;
|
||||
readonly areSomeEntitiesSelected$ = this._areSomeEntitiesSelected$;
|
||||
readonly notAllEntitiesSelected$ = this._notAllEntitiesSelected$;
|
||||
|
||||
constructor(private readonly _filterService: FilterService, private readonly _searchService: SearchService<T>) {
|
||||
// setInterval(() => {
|
||||
// console.log('All entities subs: ', this._allEntities$.observers);
|
||||
// console.log('Displayed entities subs: ', this._displayedEntities$.observers);
|
||||
// console.log('Selected entities subs: ', this._selectedEntities$.observers);
|
||||
// }, 10000);
|
||||
}
|
||||
|
||||
get filteredEntities(): T[] {
|
||||
return Object.values(this.filteredEntities$.getValue());
|
||||
get allEntities(): T[] {
|
||||
return Object.values(this._allEntities$.getValue());
|
||||
}
|
||||
|
||||
get selectedEntitiesIds(): string[] {
|
||||
return Object.values(this.selectedEntitiesIds$.getValue());
|
||||
get selectedEntities(): T[] {
|
||||
return Object.values(this._selectedEntities$.getValue());
|
||||
}
|
||||
|
||||
get displayedEntities(): T[] {
|
||||
return Object.values(this.displayedEntities$.getValue());
|
||||
return Object.values(this._displayedEntities$.getValue());
|
||||
}
|
||||
|
||||
map<K>(func: (state: T[]) => K): Observable<K> {
|
||||
return this.entities$.asObservable().pipe(
|
||||
map((state: T[]) => func(state)),
|
||||
setEntities(newEntities: Partial<T[]>): void {
|
||||
this._allEntities$.next(newEntities);
|
||||
}
|
||||
|
||||
setSelectedEntities(newEntities: Partial<T[]>): void {
|
||||
this._selectedEntities$.next(newEntities);
|
||||
}
|
||||
|
||||
isSelected(entity: T): boolean {
|
||||
return this.selectedEntities.indexOf(entity) !== -1;
|
||||
}
|
||||
|
||||
selectEntities(entities?: T[]): void {
|
||||
if (entities !== undefined && entities !== null && entities.length > 0) {
|
||||
return entities.forEach(entity => this._selectOne(entity));
|
||||
}
|
||||
return this._selectAll();
|
||||
}
|
||||
|
||||
updateSelection(): void {
|
||||
const items = this.displayedEntities.filter(item => this.selectedEntities.includes(item));
|
||||
this.setSelectedEntities(items);
|
||||
}
|
||||
|
||||
logCurrentState(): void {
|
||||
console.log('Entities', this.allEntities);
|
||||
console.log('Displayed', this.displayedEntities);
|
||||
console.log('Selected', this.selectedEntities);
|
||||
}
|
||||
|
||||
get _getDisplayedEntities$(): Observable<T[]> {
|
||||
const filterGroups$ = this._filterService.filterGroups$;
|
||||
const searchValue$ = this._searchService.valueChanges$;
|
||||
|
||||
return combineLatest([this.allEntities$, filterGroups$, searchValue$]).pipe(
|
||||
map(([entities, filterGroups]) => getFilteredEntities(entities, filterGroups)),
|
||||
map(entities => this._searchService.searchIn(entities)),
|
||||
tap(entities => this._displayedEntities$.next(entities))
|
||||
);
|
||||
}
|
||||
|
||||
private get _allEntitiesLength$(): Observable<number> {
|
||||
return this.allEntities$.pipe(map(toLengthValue), distinctUntilChanged());
|
||||
}
|
||||
|
||||
private get _displayedLength$(): Observable<number> {
|
||||
return this.displayedEntities$.pipe(map(toLengthValue), distinctUntilChanged());
|
||||
}
|
||||
|
||||
private get _selectedLength$(): Observable<number> {
|
||||
return this.selectedEntities$.pipe(map(toLengthValue), distinctUntilChanged());
|
||||
}
|
||||
|
||||
private get _areAllEntitiesSelected$(): Observable<boolean> {
|
||||
return combineLatest([this.displayedLength$, this.selectedLength$]).pipe(
|
||||
map(([displayedLength, selectedLength]) => displayedLength && displayedLength === selectedLength),
|
||||
distinctUntilChanged()
|
||||
);
|
||||
}
|
||||
|
||||
setEntities(newEntities: Partial<T[]>): void {
|
||||
this.entities$.next(newEntities);
|
||||
/**
|
||||
* Indicates that some entities are selected. If all are selected this returns true
|
||||
*/
|
||||
private get _areSomeEntitiesSelected$(): Observable<boolean> {
|
||||
return this.selectedLength$.pipe(
|
||||
map(value => !!value),
|
||||
distinctUntilChanged()
|
||||
);
|
||||
}
|
||||
|
||||
setFilteredEntities(newEntities: Partial<T[]>): void {
|
||||
this.filteredEntities$.next(newEntities);
|
||||
/**
|
||||
* Indicates that some entities are selected, but not all
|
||||
*/
|
||||
private get _notAllEntitiesSelected$(): Observable<boolean> {
|
||||
return combineLatest([this.areAllEntitiesSelected$, this.areSomeEntitiesSelected$]).pipe(
|
||||
map(([allEntitiesAreSelected, someEntitiesAreSelected]) => !allEntitiesAreSelected && someEntitiesAreSelected),
|
||||
distinctUntilChanged()
|
||||
);
|
||||
}
|
||||
|
||||
setSelectedEntitiesIds(newEntities: Partial<string[]>): void {
|
||||
this.selectedEntitiesIds$.next(newEntities);
|
||||
private get _noData$(): Observable<boolean> {
|
||||
return this.allEntitiesLength$.pipe(
|
||||
map(length => length === 0),
|
||||
distinctUntilChanged()
|
||||
);
|
||||
}
|
||||
|
||||
setDisplayedEntities(newEntities: Partial<T[]>): void {
|
||||
this.displayedEntities$.next(newEntities);
|
||||
}
|
||||
|
||||
setIdKey(value: string): void {
|
||||
this._idKey = value;
|
||||
}
|
||||
|
||||
get areAllEntitiesSelected(): boolean {
|
||||
return this.displayedEntities.length !== 0 && this.selectedEntitiesIds.length === this.displayedEntities.length;
|
||||
}
|
||||
|
||||
get areSomeEntitiesSelected$(): Observable<boolean> {
|
||||
return this.selectedEntitiesIds$.pipe(map(all => all.length > 0));
|
||||
}
|
||||
|
||||
isSelected(entity: T): boolean {
|
||||
return this.selectedEntitiesIds.indexOf(entity[this._getIdKey]) !== -1;
|
||||
}
|
||||
|
||||
toggleEntitySelected(entity: T): void {
|
||||
const currentEntityIdx = this.selectedEntitiesIds.indexOf(entity[this._getIdKey]);
|
||||
private _selectOne(entity: T): void {
|
||||
const currentEntityIdx = this.selectedEntities.indexOf(entity);
|
||||
if (currentEntityIdx === -1) {
|
||||
const currentEntityId = entity[this._getIdKey];
|
||||
return this.setSelectedEntitiesIds([...this.selectedEntitiesIds, currentEntityId]);
|
||||
return this.setSelectedEntities([...this.selectedEntities, entity]);
|
||||
}
|
||||
|
||||
this.setSelectedEntitiesIds(this.selectedEntitiesIds.filter((el, idx) => idx !== currentEntityIdx));
|
||||
this.setSelectedEntities(this.selectedEntities.filter((el, idx) => idx !== currentEntityIdx));
|
||||
}
|
||||
|
||||
toggleSelectAll(): void {
|
||||
if (this.areAllEntitiesSelected) return this.setSelectedEntitiesIds([]);
|
||||
this.setSelectedEntitiesIds(this._displayedEntitiesIds);
|
||||
private _selectAll(): void {
|
||||
if (this._allEntitiesSelected) return this.setSelectedEntities([]);
|
||||
this.setSelectedEntities(this.displayedEntities);
|
||||
}
|
||||
|
||||
updateSelection(): void {
|
||||
if (!this._idKey) return;
|
||||
|
||||
const ids = this._displayedEntitiesIds.filter(id => this.selectedEntitiesIds.includes(id));
|
||||
this.setSelectedEntitiesIds(ids);
|
||||
}
|
||||
|
||||
logCurrentState(): void {
|
||||
console.log('Entities', this.entities);
|
||||
console.log('Displayed', this.displayedEntities);
|
||||
console.log('Filtered', this.filteredEntities);
|
||||
console.log('Selected', this.selectedEntitiesIds);
|
||||
}
|
||||
|
||||
private get _displayedEntitiesIds(): string[] {
|
||||
return this.displayedEntities.map(entity => entity[this._getIdKey]);
|
||||
}
|
||||
|
||||
private get _getIdKey(): string {
|
||||
if (!this._idKey) throw new Error('Not implemented');
|
||||
|
||||
return this._idKey;
|
||||
private get _allEntitiesSelected() {
|
||||
return this.displayedEntities.length !== 0 && this.displayedEntities.length === this.selectedEntities.length;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,48 +1,30 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { debounce } from '@utils/debounce';
|
||||
import { ScreenStateService } from '@shared/services/screen-state.service';
|
||||
import { FormBuilder } from '@angular/forms';
|
||||
import { startWith } from 'rxjs/operators';
|
||||
|
||||
@Injectable()
|
||||
export class SearchService<T> {
|
||||
private _searchValue = '';
|
||||
private _searchKey: string;
|
||||
|
||||
readonly searchForm = this._formBuilder.group({
|
||||
query: ['']
|
||||
});
|
||||
|
||||
constructor(private readonly _screenStateService: ScreenStateService<T>, private readonly _formBuilder: FormBuilder) {
|
||||
this.searchForm.valueChanges.subscribe(() => this.executeSearch());
|
||||
}
|
||||
readonly valueChanges$ = this.searchForm.get('query').valueChanges.pipe(startWith(''));
|
||||
|
||||
@debounce(200)
|
||||
executeSearch(): void {
|
||||
this._searchValue = this.searchValue.toLowerCase();
|
||||
this.executeSearchImmediately();
|
||||
}
|
||||
constructor(private readonly _formBuilder: FormBuilder) {}
|
||||
|
||||
executeSearchImmediately(): void {
|
||||
const displayed = this._screenStateService.filteredEntities || this._screenStateService.entities;
|
||||
searchIn(entities: T[]) {
|
||||
if (!this._searchKey) return entities;
|
||||
|
||||
if (!this._searchKey) {
|
||||
return this._screenStateService.setDisplayedEntities(displayed);
|
||||
}
|
||||
|
||||
this._screenStateService.setDisplayedEntities(
|
||||
displayed.filter(entity => this._searchField(entity).toLowerCase().includes(this._searchValue))
|
||||
);
|
||||
this._screenStateService.updateSelection();
|
||||
const searchValue = this.searchValue.toLowerCase();
|
||||
return entities.filter(entity => this._searchField(entity).includes(searchValue));
|
||||
}
|
||||
|
||||
setSearchKey(value: string): void {
|
||||
this._searchKey = value;
|
||||
}
|
||||
|
||||
get isSearchNeeded(): boolean {
|
||||
return !!this._searchKey;
|
||||
}
|
||||
|
||||
get searchValue(): string {
|
||||
return this.searchForm.get('query').value;
|
||||
}
|
||||
@ -51,7 +33,7 @@ export class SearchService<T> {
|
||||
this.searchForm.reset({ query: '' });
|
||||
}
|
||||
|
||||
protected _searchField(entity: T): string {
|
||||
return entity[this._searchKey];
|
||||
private _searchField(entity: T): string {
|
||||
return entity[this._searchKey].toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,7 +24,7 @@ import { DictionaryAnnotationIconComponent } from './components/dictionary-annot
|
||||
import { HiddenActionComponent } from './components/hidden-action/hidden-action.component';
|
||||
import { ConfirmationDialogComponent } from './dialogs/confirmation-dialog/confirmation-dialog.component';
|
||||
import { EmptyStateComponent } from './components/empty-state/empty-state.component';
|
||||
import { SortByPipe } from './components/sort-pipe/sort-by.pipe';
|
||||
import { SortByPipe } from './pipes/sort-by.pipe';
|
||||
import { RoundCheckboxComponent } from './components/checkbox/round-checkbox.component';
|
||||
import { DateAdapter, MAT_DATE_FORMATS, MAT_DATE_LOCALE } from '@angular/material/core';
|
||||
import { MomentDateAdapter } from '@angular/material-moment-adapter';
|
||||
@ -39,6 +39,7 @@ import { AssignUserDropdownComponent } from './components/assign-user-dropdown/a
|
||||
import { InputWithActionComponent } from '@shared/components/input-with-action/input-with-action.component';
|
||||
import { PageHeaderComponent } from './components/page-header/page-header.component';
|
||||
import { DatePipe } from '@shared/pipes/date.pipe';
|
||||
import { TableHeaderComponent } from './components/table-header/table-header.component';
|
||||
|
||||
const buttons = [ChevronButtonComponent, CircleButtonComponent, FileDownloadBtnComponent, IconButtonComponent, UserButtonComponent];
|
||||
|
||||
@ -73,9 +74,9 @@ const utils = [HumanizePipe, DatePipe, SyncWidthDirective, HasScrollbarDirective
|
||||
const modules = [MatConfigModule, TranslateModule, ScrollingModule, IconsModule, FormsModule, ReactiveFormsModule];
|
||||
|
||||
@NgModule({
|
||||
declarations: [...components, ...utils],
|
||||
declarations: [...components, ...utils, TableHeaderComponent],
|
||||
imports: [CommonModule, ...modules, MonacoEditorModule],
|
||||
exports: [...modules, ...components, ...utils],
|
||||
exports: [...modules, ...components, ...utils, TableHeaderComponent],
|
||||
providers: [
|
||||
{ provide: DateAdapter, useClass: MomentDateAdapter, deps: [MAT_DATE_LOCALE] },
|
||||
{
|
||||
|
||||
@ -8,11 +8,11 @@ import { HttpErrorResponse } from '@angular/common/http';
|
||||
export class ErrorMessageService {
|
||||
constructor(private readonly _translateService: TranslateService) {}
|
||||
|
||||
_parseErrorResponse(err: HttpErrorResponse) {
|
||||
_parseErrorResponse(err: HttpErrorResponse): string {
|
||||
return err?.error?.message?.includes('message') ? ` ${err.error.message.match('"message":"(.*?)\\"')[1]}` : '';
|
||||
}
|
||||
|
||||
getMessage(err: HttpErrorResponse, defaultMessage: string) {
|
||||
return this._translateService.instant(defaultMessage) + this._parseErrorResponse(err);
|
||||
getMessage(error: HttpErrorResponse, defaultMessage: string): string {
|
||||
return this._translateService.instant(defaultMessage) + this._parseErrorResponse(error);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { EventEmitter, Injectable } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
|
||||
const MIN_LOADING_TIME = 300;
|
||||
|
||||
@ -7,16 +7,15 @@ const MIN_LOADING_TIME = 300;
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class LoadingService {
|
||||
private readonly _loadingEvent = new EventEmitter();
|
||||
private readonly _loadingEvent = new BehaviorSubject(false);
|
||||
private _loadingStarted: number;
|
||||
|
||||
get isLoading(): Observable<boolean> {
|
||||
return this._loadingEvent;
|
||||
return this._loadingEvent.asObservable();
|
||||
}
|
||||
|
||||
start(): void {
|
||||
// setTimeout is used so that value doesn't change after it was checked for changes
|
||||
setTimeout(() => this._loadingEvent.next(true));
|
||||
this._loadingEvent.next(true);
|
||||
this._loadingStarted = new Date().getTime();
|
||||
}
|
||||
|
||||
@ -37,7 +36,7 @@ export class LoadingService {
|
||||
}
|
||||
|
||||
private _stop() {
|
||||
this._loadingEvent.next(false);
|
||||
setTimeout(() => this._loadingEvent.next(false));
|
||||
}
|
||||
|
||||
private _stopAfter(timeout: number) {
|
||||
|
||||
@ -1,47 +0,0 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ActiveToast, ToastrService } from 'ngx-toastr';
|
||||
import { IndividualConfig } from 'ngx-toastr/toastr/toastr-config';
|
||||
import { NavigationStart, Router } from '@angular/router';
|
||||
|
||||
export enum NotificationType {
|
||||
SUCCESS = 'SUCCESS',
|
||||
WARNING = 'WARNING',
|
||||
ERROR = 'ERROR',
|
||||
INFO = 'INFO'
|
||||
}
|
||||
|
||||
export class ToastAction {
|
||||
title: string;
|
||||
action?: () => any;
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class NotificationService {
|
||||
constructor(private readonly _toastr: ToastrService, private readonly _router: Router) {
|
||||
_router.events.subscribe(event => {
|
||||
if (event instanceof NavigationStart) {
|
||||
this._toastr.clear();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
showToastNotification(
|
||||
message: string,
|
||||
title?: string,
|
||||
notificationType = NotificationType.INFO,
|
||||
options?: Partial<IndividualConfig> & { actions?: ToastAction[] }
|
||||
): ActiveToast<any> {
|
||||
switch (notificationType) {
|
||||
case NotificationType.ERROR:
|
||||
return this._toastr.error(message, title, options);
|
||||
case NotificationType.SUCCESS:
|
||||
return this._toastr.success(message, title, options);
|
||||
case NotificationType.WARNING:
|
||||
return this._toastr.warning(message, title, options);
|
||||
case NotificationType.INFO:
|
||||
return this._toastr.info(message, title, options);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,8 +1,9 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { orderBy } from 'lodash';
|
||||
|
||||
export type SortingOrder = 'asc' | 'desc';
|
||||
|
||||
export enum SortingOrders {
|
||||
export const enum SortingOrders {
|
||||
ASC = 'asc',
|
||||
DESC = 'desc'
|
||||
}
|
||||
@ -12,59 +13,52 @@ export interface SortingOption {
|
||||
column: string;
|
||||
}
|
||||
|
||||
export type ScreenName =
|
||||
| 'dossier-listing'
|
||||
| 'dossier-overview'
|
||||
| 'dictionary-listing'
|
||||
| 'dossier-templates-listing'
|
||||
| 'default-colors'
|
||||
| 'file-attributes-listing'
|
||||
| 'dossier-attributes-listing';
|
||||
|
||||
export enum ScreenNames {
|
||||
DOSSIER_LISTING = 'dossier-listing',
|
||||
DOSSIER_OVERVIEW = 'dossier-overview',
|
||||
DICTIONARY_LISTING = 'dictionary-listing',
|
||||
DOSSIER_TEMPLATES_LISTING = 'dossier-templates-listing',
|
||||
DEFAULT_COLORS = 'default-colors',
|
||||
FILE_ATTRIBUTES_LISTING = 'file-attributes-listing',
|
||||
DOSSIER_ATTRIBUTES_LISTING = 'dossier-attributes-listing'
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class SortingService {
|
||||
private _currentScreenName: string;
|
||||
private readonly _options: { [key in ScreenName]: SortingOption } = {
|
||||
[ScreenNames.DOSSIER_LISTING]: { column: 'dossier.dossierName', order: SortingOrders.ASC },
|
||||
[ScreenNames.DOSSIER_OVERVIEW]: { column: 'filename', order: SortingOrders.ASC },
|
||||
[ScreenNames.DICTIONARY_LISTING]: { column: 'label', order: SortingOrders.ASC },
|
||||
[ScreenNames.DOSSIER_TEMPLATES_LISTING]: { column: 'name', order: SortingOrders.ASC },
|
||||
[ScreenNames.DEFAULT_COLORS]: { column: 'key', order: SortingOrders.ASC },
|
||||
[ScreenNames.FILE_ATTRIBUTES_LISTING]: { column: 'label', order: SortingOrders.ASC },
|
||||
[ScreenNames.DOSSIER_ATTRIBUTES_LISTING]: { column: 'label', order: 'asc' }
|
||||
};
|
||||
private _sortingOption: SortingOption;
|
||||
|
||||
setScreenName(value: string) {
|
||||
this._currentScreenName = value;
|
||||
setSortingOption(value: SortingOption): void {
|
||||
this._sortingOption = value;
|
||||
}
|
||||
|
||||
sort<T>(values: T[], order = '', column: string = ''): T[] {
|
||||
if (!values || order === '' || !order) {
|
||||
return values;
|
||||
} // no array
|
||||
if (!column || column === '') {
|
||||
if (order === SortingOrders.ASC) {
|
||||
return values.sort();
|
||||
} else {
|
||||
return values.sort().reverse();
|
||||
}
|
||||
} // sort 1d array
|
||||
if (values.length <= 1) {
|
||||
return values;
|
||||
} // array with only one item
|
||||
return orderBy(values, [column], [order]);
|
||||
}
|
||||
|
||||
defaultSort<T>(values: T[]) {
|
||||
return this.sort(values, this.sortingOption?.order, this.sortingOption?.column);
|
||||
}
|
||||
|
||||
toggleSort(column: string) {
|
||||
if (this._options[this._currentScreenName].column === column) {
|
||||
if (this._sortingOption.column === column) {
|
||||
this._currentOrder = this._currentOrder === SortingOrders.ASC ? SortingOrders.DESC : SortingOrders.ASC;
|
||||
} else {
|
||||
this._options[this._currentScreenName] = { column, order: SortingOrders.ASC };
|
||||
this._sortingOption = { column, order: SortingOrders.ASC };
|
||||
}
|
||||
}
|
||||
|
||||
getSortingOption() {
|
||||
return this._options[this._currentScreenName];
|
||||
get sortingOption(): SortingOption {
|
||||
return this._sortingOption;
|
||||
}
|
||||
|
||||
private get _currentOrder(): string {
|
||||
return this._options[this._currentScreenName].order;
|
||||
private get _currentOrder(): SortingOrder {
|
||||
return this._sortingOption.order;
|
||||
}
|
||||
|
||||
private set _currentOrder(value: string) {
|
||||
this._options[this._currentScreenName].order = value;
|
||||
private set _currentOrder(value: SortingOrder) {
|
||||
this._sortingOption.order = value;
|
||||
}
|
||||
}
|
||||
|
||||
82
apps/red-ui/src/app/services/toaster.service.ts
Normal file
82
apps/red-ui/src/app/services/toaster.service.ts
Normal file
@ -0,0 +1,82 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ActiveToast, ToastrService } from 'ngx-toastr';
|
||||
import { IndividualConfig } from 'ngx-toastr/toastr/toastr-config';
|
||||
import { NavigationStart, Router } from '@angular/router';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { HttpErrorResponse } from '@angular/common/http';
|
||||
import { ErrorMessageService } from '@services/error-message.service';
|
||||
import { filter } from 'rxjs/operators';
|
||||
|
||||
const enum NotificationType {
|
||||
SUCCESS = 'SUCCESS',
|
||||
WARNING = 'WARNING',
|
||||
INFO = 'INFO'
|
||||
}
|
||||
|
||||
export interface ToasterOptions extends IndividualConfig {
|
||||
title?: string;
|
||||
/**
|
||||
* These params are used as interpolateParams for translate service
|
||||
*/
|
||||
params?: object;
|
||||
actions?: { title?: string; action: () => void }[];
|
||||
}
|
||||
|
||||
export interface ErrorToasterOptions extends ToasterOptions {
|
||||
/**
|
||||
* Pass an http error that will be processed by error message service and shown in toast
|
||||
*/
|
||||
error?: HttpErrorResponse;
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class Toaster {
|
||||
constructor(
|
||||
private readonly _toastr: ToastrService,
|
||||
private readonly _router: Router,
|
||||
private readonly _translateService: TranslateService,
|
||||
private readonly _errorMessageService: ErrorMessageService
|
||||
) {
|
||||
_router.events.pipe(filter(event => event instanceof NavigationStart)).subscribe(() => {
|
||||
_toastr.clear();
|
||||
});
|
||||
}
|
||||
|
||||
error(message: string, options?: Partial<ErrorToasterOptions>) {
|
||||
if (options?.error) message = this._errorMessageService.getMessage(options.error, message);
|
||||
else message = this._translateService.instant(message, options?.params);
|
||||
|
||||
return this._toastr.error(message, options?.title, options);
|
||||
}
|
||||
|
||||
info(message: string, options?: Partial<ToasterOptions>) {
|
||||
return this._showToastNotification(message, NotificationType.INFO, options);
|
||||
}
|
||||
|
||||
success(message: string, options?: Partial<ToasterOptions>) {
|
||||
return this._showToastNotification(message, NotificationType.SUCCESS, options);
|
||||
}
|
||||
|
||||
warning(message: string, options?: Partial<ToasterOptions>) {
|
||||
return this._showToastNotification(message, NotificationType.WARNING, options);
|
||||
}
|
||||
|
||||
private _showToastNotification(
|
||||
message: string,
|
||||
notificationType = NotificationType.INFO,
|
||||
options?: Partial<ToasterOptions>
|
||||
): ActiveToast<unknown> {
|
||||
message = this._translateService.instant(message, options?.params);
|
||||
|
||||
switch (notificationType) {
|
||||
case NotificationType.SUCCESS:
|
||||
return this._toastr.success(message, options?.title, options);
|
||||
case NotificationType.WARNING:
|
||||
return this._toastr.warning(message, options?.title, options);
|
||||
case NotificationType.INFO:
|
||||
return this._toastr.info(message, options?.title, options);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,15 +1,36 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { UserPreferenceControllerService } from '@redaction/red-ui-http';
|
||||
|
||||
interface UserAttributes {
|
||||
[p: string]: string[];
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class UserPreferenceService {
|
||||
private _userAttributes: UserAttributes = {};
|
||||
|
||||
constructor(private readonly _userPreferenceControllerService: UserPreferenceControllerService) {
|
||||
_userPreferenceControllerService.getAllUserAttributes().subscribe(attributes => {
|
||||
this._userAttributes = attributes ?? {};
|
||||
});
|
||||
}
|
||||
|
||||
get userAttributes(): UserAttributes {
|
||||
return this._userAttributes;
|
||||
}
|
||||
|
||||
get areDevFeaturesEnabled() {
|
||||
const value = sessionStorage.getItem('redaction.enable-dev-features');
|
||||
if (value) {
|
||||
return value === 'true';
|
||||
return value ? value === 'true' : false;
|
||||
}
|
||||
|
||||
getLastOpenedFileId(key: string): string {
|
||||
if (this.userAttributes[key]?.length > 0) {
|
||||
return this.userAttributes[key][0];
|
||||
}
|
||||
return false;
|
||||
return '';
|
||||
}
|
||||
|
||||
toggleDevFeatures() {
|
||||
|
||||
@ -16,7 +16,7 @@ export interface ProfileModel {
|
||||
export class UserWrapper {
|
||||
name: string;
|
||||
|
||||
constructor(private _currentUser: KeycloakProfile, public roles: string[], public id: string) {
|
||||
constructor(private readonly _currentUser: KeycloakProfile, public roles: string[], public id: string) {
|
||||
this.name = this.firstName && this.lastName ? `${this.firstName} ${this.lastName}` : this.username;
|
||||
}
|
||||
|
||||
@ -150,8 +150,8 @@ export class UserService {
|
||||
return this.getName(this.getUserById(userId));
|
||||
}
|
||||
|
||||
getName(user?: User) {
|
||||
return user?.firstName && user?.lastName ? `${user.firstName} ${user.lastName}` : user?.username;
|
||||
getName({ firstName, lastName, username }: User = {}) {
|
||||
return firstName && lastName ? `${firstName} ${lastName}` : username;
|
||||
}
|
||||
|
||||
isManager(user: User | UserWrapper = this.user): boolean {
|
||||
|
||||
@ -2,7 +2,6 @@ import { EventEmitter, Injectable } from '@angular/core';
|
||||
import {
|
||||
DictionaryControllerService,
|
||||
Dossier,
|
||||
DossierControllerService,
|
||||
DossierTemplateControllerService,
|
||||
FileAttributesConfig,
|
||||
FileAttributesControllerService,
|
||||
@ -10,7 +9,7 @@ import {
|
||||
ReanalysisControllerService,
|
||||
StatusControllerService
|
||||
} from '@redaction/red-ui-http';
|
||||
import { NotificationService, NotificationType } from '@services/notification.service';
|
||||
import { Toaster } from '../services/toaster.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Event, NavigationEnd, ResolveStart, Router } from '@angular/router';
|
||||
import { UserService } from '@services/user.service';
|
||||
@ -21,6 +20,7 @@ import { FileStatusWrapper } from '@models/file/file-status.wrapper';
|
||||
import { DossierWrapper } from './model/dossier.wrapper';
|
||||
import { TypeValueWrapper } from '@models/file/type-value.wrapper';
|
||||
import { DossierTemplateModelWrapper } from '@models/file/dossier-template-model.wrapper';
|
||||
import { DossiersService } from '../modules/dossier/services/dossiers.service';
|
||||
|
||||
export interface AppState {
|
||||
dossiers: DossierWrapper[];
|
||||
@ -46,8 +46,8 @@ export class AppStateService {
|
||||
constructor(
|
||||
private readonly _router: Router,
|
||||
private readonly _userService: UserService,
|
||||
private readonly _dossierControllerService: DossierControllerService,
|
||||
private readonly _notificationService: NotificationService,
|
||||
private readonly _dossiersService: DossiersService,
|
||||
private readonly _toaster: Toaster,
|
||||
private readonly _reanalysisControllerService: ReanalysisControllerService,
|
||||
private readonly _translateService: TranslateService,
|
||||
private readonly _dictionaryControllerService: DictionaryControllerService,
|
||||
@ -65,15 +65,15 @@ export class AppStateService {
|
||||
activeDictionaryType: null
|
||||
};
|
||||
|
||||
this._router.events.subscribe((event: Event) => {
|
||||
_router.events.subscribe((event: Event) => {
|
||||
if (AppStateService._isFileOverviewRoute(event)) {
|
||||
const url = (event as ResolveStart).url.replace('/main/dossiers/', '');
|
||||
const [dossierId, , fileId] = url.split(/[/?]/);
|
||||
this.activateFile(dossierId, fileId);
|
||||
return this.activateFile(dossierId, fileId);
|
||||
}
|
||||
if (AppStateService._isDossierOverviewRoute(event)) {
|
||||
const dossierId = (event as ResolveStart).url.replace('/main/dossiers/', '');
|
||||
this.activateDossier(dossierId);
|
||||
return this.activateDossier(dossierId);
|
||||
}
|
||||
if (AppStateService._isRandomRoute(event)) {
|
||||
this._appState.activeDossierId = null;
|
||||
@ -88,11 +88,7 @@ export class AppStateService {
|
||||
}
|
||||
|
||||
get aggregatedFiles(): FileStatusWrapper[] {
|
||||
const result: FileStatusWrapper[] = [];
|
||||
this._appState.dossiers.forEach(p => {
|
||||
result.push(...p.files);
|
||||
});
|
||||
return result;
|
||||
return this.allDossiers.reduce((acc, { files }) => [...acc, ...files], []);
|
||||
}
|
||||
|
||||
get activeDossierTemplateId(): string {
|
||||
@ -128,7 +124,7 @@ export class AppStateService {
|
||||
}
|
||||
|
||||
get activeDossier(): DossierWrapper | undefined {
|
||||
return this._appState.dossiers.find(p => p.dossierId === this.activeDossierId);
|
||||
return this.allDossiers.find(p => p.dossierId === this.activeDossierId);
|
||||
}
|
||||
|
||||
get allDossiers(): DossierWrapper[] {
|
||||
@ -147,18 +143,14 @@ export class AppStateService {
|
||||
return this._appState.activeFileId;
|
||||
}
|
||||
|
||||
get totalAnalysedPages() {
|
||||
get totalAnalysedPages(): number {
|
||||
return this._appState.totalAnalysedPages;
|
||||
}
|
||||
|
||||
get totalPeople() {
|
||||
get totalPeople(): number {
|
||||
return this._appState.totalPeople;
|
||||
}
|
||||
|
||||
get totalDocuments() {
|
||||
return this._appState.totalDocuments;
|
||||
}
|
||||
|
||||
private static _isFileOverviewRoute(event: Event) {
|
||||
return event instanceof ResolveStart && event.url.includes('/main/dossiers/') && event.url.includes('/file/');
|
||||
}
|
||||
@ -177,18 +169,15 @@ export class AppStateService {
|
||||
}
|
||||
}
|
||||
|
||||
getDictionaryColor(type?: string, dossierTemplateId?: string) {
|
||||
if (!dossierTemplateId && this.activeDossier) {
|
||||
dossierTemplateId = this.activeDossier.dossierTemplateId;
|
||||
}
|
||||
getDictionaryColor(type?: string, dossierTemplateId = this.activeDossier?.dossierTemplateId) {
|
||||
if (!dossierTemplateId) {
|
||||
dossierTemplateId = this.dossierTemplates.length > 0 ? this.dossierTemplates[0].dossierTemplateId : undefined;
|
||||
dossierTemplateId = this.dossierTemplates[0]?.dossierTemplateId;
|
||||
}
|
||||
if (!dossierTemplateId) {
|
||||
return undefined;
|
||||
}
|
||||
const color = this._dictionaryData[dossierTemplateId][type]?.hexColor;
|
||||
return color ? color : this._dictionaryData[dossierTemplateId]['default'].hexColor;
|
||||
return color ?? this._dictionaryData[dossierTemplateId]['default'].hexColor;
|
||||
}
|
||||
|
||||
getDossierTemplateById(id: string): DossierTemplateModelWrapper {
|
||||
@ -220,20 +209,21 @@ export class AppStateService {
|
||||
}
|
||||
|
||||
async loadAllDossiers(emitEvents: boolean = true) {
|
||||
const dossiers = await this._dossierControllerService.getDossiers().toPromise();
|
||||
if (dossiers) {
|
||||
const mappedDossiers = dossiers.map(p => new DossierWrapper(p, this._getExistingFiles(p.dossierId)));
|
||||
|
||||
const fileData = await this._statusControllerService.getFileStatusForDossiers(mappedDossiers.map(p => p.dossierId)).toPromise();
|
||||
|
||||
for (const dossierId of Object.keys(fileData)) {
|
||||
const dossier = mappedDossiers.find(p => p.dossierId === dossierId);
|
||||
this._processFiles(dossier, fileData[dossierId], emitEvents);
|
||||
}
|
||||
|
||||
this._appState.dossiers = mappedDossiers;
|
||||
this._computeStats();
|
||||
const dossiers = await this._dossiersService.getAll();
|
||||
if (!dossiers) {
|
||||
return;
|
||||
}
|
||||
|
||||
const mappedDossiers = dossiers.map(p => new DossierWrapper(p, this._getExistingFiles(p.dossierId)));
|
||||
const fileData = await this._statusControllerService.getFileStatusForDossiers(mappedDossiers.map(p => p.dossierId)).toPromise();
|
||||
|
||||
for (const dossierId of Object.keys(fileData)) {
|
||||
const dossier = mappedDossiers.find(p => p.dossierId === dossierId);
|
||||
this._processFiles(dossier, fileData[dossierId], emitEvents);
|
||||
}
|
||||
|
||||
this._appState.dossiers = mappedDossiers;
|
||||
this._computeStats();
|
||||
}
|
||||
|
||||
async reloadActiveFile() {
|
||||
@ -246,10 +236,10 @@ export class AppStateService {
|
||||
const activeFileWrapper = new FileStatusWrapper(
|
||||
activeFile,
|
||||
this._userService.getNameForId(activeFile.currentReviewer),
|
||||
this.activeDossier.dossierTemplateId,
|
||||
this._appState.fileAttributesConfig[this.activeDossier.dossierTemplateId]
|
||||
this.activeDossierTemplateId,
|
||||
this._appState.fileAttributesConfig[this.activeDossierTemplateId]
|
||||
);
|
||||
this.activeDossier.files = this.activeDossier.files.map(file =>
|
||||
this.activeDossier.files = this.activeDossier?.files.map(file =>
|
||||
file.fileId === activeFileWrapper.fileId ? activeFileWrapper : file
|
||||
);
|
||||
|
||||
@ -261,32 +251,26 @@ export class AppStateService {
|
||||
return activeFileWrapper;
|
||||
}
|
||||
|
||||
async getFiles(dossier?: DossierWrapper, emitEvents: boolean = true) {
|
||||
if (!dossier) {
|
||||
dossier = this.activeDossier;
|
||||
}
|
||||
async getFiles(dossier: DossierWrapper = this.activeDossier, emitEvents = true) {
|
||||
const files = await this._statusControllerService.getDossierStatus(dossier.dossierId).toPromise();
|
||||
|
||||
return this._processFiles(dossier, files, emitEvents);
|
||||
}
|
||||
|
||||
async reanalyzeDossier(dossier?: DossierWrapper) {
|
||||
if (!dossier) {
|
||||
dossier = this.activeDossier;
|
||||
}
|
||||
await this._reanalysisControllerService.reanalyzeDossier(dossier.dossierId).toPromise();
|
||||
async reanalyzeDossier({ dossierId }: DossierWrapper = this.activeDossier) {
|
||||
await this._reanalysisControllerService.reanalyzeDossier(dossierId).toPromise();
|
||||
}
|
||||
|
||||
activateDossier(dossierId: string) {
|
||||
activateDossier(dossierId: string): void {
|
||||
this._appState.activeFileId = null;
|
||||
this._appState.activeDossierId = dossierId;
|
||||
if (!this.activeDossier) {
|
||||
this._appState.activeDossierId = null;
|
||||
this._router.navigate(['/main/dossiers']);
|
||||
this._router.navigate(['/main/dossiers']).then();
|
||||
return;
|
||||
} else {
|
||||
this.updateDossierDictionary(this.activeDossier.dossierTemplateId, dossierId);
|
||||
}
|
||||
|
||||
this.updateDossierDictionary(this.activeDossier.dossierTemplateId, dossierId);
|
||||
}
|
||||
|
||||
updateDossierDictionary(dossierTemplateId: string, dossierId: string) {
|
||||
@ -302,7 +286,7 @@ export class AppStateService {
|
||||
}
|
||||
|
||||
activateFile(dossierId: string, fileId: string) {
|
||||
if (this._appState.activeDossierId === dossierId && this._appState.activeFileId === fileId) return;
|
||||
if (this.activeDossierId === dossierId && this.activeFileId === fileId) return;
|
||||
this.activateDossier(dossierId);
|
||||
if (this.activeDossier) {
|
||||
this._appState.activeFileId = fileId;
|
||||
@ -341,29 +325,19 @@ export class AppStateService {
|
||||
}
|
||||
|
||||
deleteDossier(dossier: DossierWrapper) {
|
||||
return this._dossierControllerService
|
||||
.deleteDossier(dossier.dossierId)
|
||||
.toPromise()
|
||||
.then(
|
||||
() => {
|
||||
const index = this._appState.dossiers.findIndex(p => p.dossier.dossierId === dossier.dossierId);
|
||||
this._appState.dossiers.splice(index, 1);
|
||||
this._appState.dossiers = [...this._appState.dossiers];
|
||||
},
|
||||
() => {
|
||||
this._notificationService.showToastNotification(
|
||||
this._translateService.instant('dossiers.delete.delete-failed', dossier),
|
||||
null,
|
||||
NotificationType.ERROR
|
||||
);
|
||||
}
|
||||
);
|
||||
return this._dossiersService.delete(dossier.dossierId).then(
|
||||
() => {
|
||||
const index = this.allDossiers.findIndex(p => p.dossierId === dossier.dossierId);
|
||||
this._appState.dossiers.splice(index, 1);
|
||||
},
|
||||
() => this._toaster.error('dossiers.delete.delete-failed', { params: dossier })
|
||||
);
|
||||
}
|
||||
|
||||
async addOrUpdateDossier(dossier: Dossier) {
|
||||
async createOrUpdateDossier(dossier: Dossier) {
|
||||
try {
|
||||
const updatedDossier = await this._dossierControllerService.createOrUpdateDossier(dossier).toPromise();
|
||||
let foundDossier = this._appState.dossiers.find(p => p.dossier.dossierId === updatedDossier.dossierId);
|
||||
const updatedDossier = await this._dossiersService.createOrUpdate(dossier);
|
||||
let foundDossier = this.allDossiers.find(p => p.dossierId === updatedDossier.dossierId);
|
||||
if (foundDossier) {
|
||||
Object.assign((foundDossier.dossier = updatedDossier));
|
||||
} else {
|
||||
@ -373,19 +347,15 @@ export class AppStateService {
|
||||
this._appState.dossiers = [...this._appState.dossiers];
|
||||
return foundDossier;
|
||||
} catch (error) {
|
||||
this._notificationService.showToastNotification(
|
||||
this._translateService.instant(
|
||||
error.status === 409 ? 'add-dossier-dialog.errors.dossier-already-exists' : 'add-dossier-dialog.errors.generic'
|
||||
),
|
||||
null,
|
||||
NotificationType.ERROR
|
||||
this._toaster.error(
|
||||
error.status === 409 ? 'add-dossier-dialog.errors.dossier-already-exists' : 'add-dossier-dialog.errors.generic'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async reloadActiveDossierFiles() {
|
||||
if (this.activeDossierId) {
|
||||
await this.getFiles(this.activeDossier);
|
||||
await this.getFiles();
|
||||
}
|
||||
}
|
||||
|
||||
@ -408,7 +378,7 @@ export class AppStateService {
|
||||
}
|
||||
|
||||
async loadAllDossiersIfNecessary() {
|
||||
if (!this._appState.dossiers.length) {
|
||||
if (!this.allDossiers.length) {
|
||||
await this.loadAllDossiers();
|
||||
}
|
||||
}
|
||||
@ -642,7 +612,7 @@ export class AppStateService {
|
||||
return [typeObs, colorsObs];
|
||||
}
|
||||
|
||||
async loadDictionaryData() {
|
||||
async loadDictionaryData(): Promise<void> {
|
||||
const obj = {};
|
||||
const observables = [];
|
||||
|
||||
@ -660,9 +630,9 @@ export class AppStateService {
|
||||
this._dictionaryData = obj;
|
||||
}
|
||||
|
||||
private _getExistingFiles(dossierId: string) {
|
||||
const found = this._appState.dossiers.find(p => p.dossier.dossierId === dossierId);
|
||||
return found ? found.files : [];
|
||||
private _getExistingFiles(dossierId: string): FileStatusWrapper[] {
|
||||
const dossier = this.allDossiers.find(p => p.dossierId === dossierId);
|
||||
return dossier?.files ?? [];
|
||||
}
|
||||
|
||||
private _processFiles(dossier: DossierWrapper, files: FileStatus[], emitEvents: boolean = true) {
|
||||
@ -727,25 +697,18 @@ export class AppStateService {
|
||||
let totalAnalysedPages = 0;
|
||||
let totalDocuments = 0;
|
||||
const totalPeople = new Set<string>();
|
||||
this._appState.dossiers.forEach(p => {
|
||||
totalDocuments += p.files.length;
|
||||
if (p.dossier.memberIds) {
|
||||
p.dossier.memberIds.forEach(m => totalPeople.add(m));
|
||||
this.allDossiers.forEach(d => {
|
||||
totalDocuments += d.files.length;
|
||||
if (d.dossier.memberIds) {
|
||||
d.dossier.memberIds.forEach(m => totalPeople.add(m));
|
||||
}
|
||||
let numberOfPages = 0;
|
||||
p.files.forEach(f => {
|
||||
numberOfPages += f.numberOfPages;
|
||||
});
|
||||
p.totalNumberOfPages = numberOfPages;
|
||||
totalAnalysedPages += numberOfPages;
|
||||
|
||||
d.totalNumberOfPages = d.files.reduce((acc, file) => acc + file.numberOfPages, 0);
|
||||
totalAnalysedPages += d.totalNumberOfPages;
|
||||
});
|
||||
|
||||
this._appState.totalPeople = totalPeople.size;
|
||||
this._appState.totalAnalysedPages = totalAnalysedPages;
|
||||
this._appState.totalDocuments = totalDocuments;
|
||||
|
||||
if (this.activeDossierId && this.activeFileId) {
|
||||
this.activateFile(this.activeDossierId, this.activeFileId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,3 +1,13 @@
|
||||
type StatusSorterItem = { key?: string } | string;
|
||||
|
||||
const byStatus = (a: StatusSorterItem, b: StatusSorterItem) => {
|
||||
if (typeof a !== typeof b) return;
|
||||
|
||||
const x = typeof a === 'string' ? a : a.key;
|
||||
const y = typeof b === 'string' ? b : b.key;
|
||||
return (StatusSorter[x] = StatusSorter[y]);
|
||||
};
|
||||
|
||||
export const StatusSorter = {
|
||||
ERROR: 0,
|
||||
UNPROCESSED: 1,
|
||||
@ -9,5 +19,5 @@ export const StatusSorter = {
|
||||
UNDER_REVIEW: 15,
|
||||
UNDER_APPROVAL: 20,
|
||||
APPROVED: 25,
|
||||
byKey: (a: { key: string }, b: { key: string }) => StatusSorter[a.key] - StatusSorter[b.key]
|
||||
byStatus: byStatus
|
||||
};
|
||||
|
||||
@ -1337,6 +1337,36 @@
|
||||
"title": "No dossiers match your current filters."
|
||||
}
|
||||
},
|
||||
"sorting": {
|
||||
"alphabetically": "Alphabetically",
|
||||
"custom": "Custom",
|
||||
"number-of-analyses": "Number of analyses",
|
||||
"number-of-pages": "Number of pages",
|
||||
"oldest": "Oldest",
|
||||
"recent": "Recent"
|
||||
},
|
||||
"submitted": "Submitted",
|
||||
"suggestion": "Suggestion for redaction",
|
||||
"top-bar": {
|
||||
"navigation-items": {
|
||||
"back": "Back",
|
||||
"dossiers": "Active Dossier",
|
||||
"my-account": {
|
||||
"children": {
|
||||
"admin": "Settings",
|
||||
"downloads": "My Downloads",
|
||||
"language": {
|
||||
"de": "German",
|
||||
"en": "English",
|
||||
"label": "Language"
|
||||
},
|
||||
"my-profile": "My Profile",
|
||||
"trash": "Trash",
|
||||
"logout": "Logout"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "Type",
|
||||
"upload-status": {
|
||||
"dialog": {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user