Merge branch 'master' into VM/HelpModeUpdates
This commit is contained in:
commit
ef2f2caa2d
@ -1,2 +1,2 @@
|
||||
<router-outlet></router-outlet>
|
||||
<redaction-full-page-loading-indicator [displayed]="loadingService.isLoading$ | async"></redaction-full-page-loading-indicator>
|
||||
<redaction-full-page-loading-indicator></redaction-full-page-loading-indicator>
|
||||
|
||||
@ -5,6 +5,7 @@ import { DownloadControllerService } from '@redaction/red-ui-http';
|
||||
import { CircleButtonTypes, DefaultListingServices, ListingComponent, TableColumnConfig } from '@iqser/common-ui';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { LoadingService } from '@services/loading.service';
|
||||
import { timer } from 'rxjs';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-downloads-list-screen',
|
||||
@ -35,6 +36,9 @@ export class DownloadsListScreenComponent extends ListingComponent<DownloadStatu
|
||||
|
||||
async ngOnInit() {
|
||||
this._loadingService.loadWhile(this._loadData());
|
||||
this.addSubscription = timer(0, 5000).subscribe(async () => {
|
||||
await this._loadData();
|
||||
});
|
||||
}
|
||||
|
||||
downloadItem(download: DownloadStatusWrapper) {
|
||||
|
||||
@ -74,7 +74,7 @@ export class TrashScreenComponent extends ListingComponent<DossierListItem> impl
|
||||
|
||||
hardDelete(dossiers = this.entitiesService.selected) {
|
||||
const data = new ConfirmationDialogInput({
|
||||
title: dossiers.length > 1 ? _('confirmation-dialog.delete-dossier.title-alt') : _('confirmation-dialog.delete-dossier.title'),
|
||||
title: _('confirmation-dialog.delete-dossier.title'),
|
||||
titleColor: TitleColors.PRIMARY,
|
||||
question: _('confirmation-dialog.delete-dossier.question'),
|
||||
// details: _('confirmation-dialog.delete-dossier.details'),
|
||||
@ -83,7 +83,7 @@ export class TrashScreenComponent extends ListingComponent<DossierListItem> impl
|
||||
denyText: _('confirmation-dialog.delete-dossier.deny-text'),
|
||||
translateParams: {
|
||||
dossierName: dossiers[0].dossierName,
|
||||
period: this._deleteRetentionHours
|
||||
dossiersCount: dossiers.length
|
||||
}
|
||||
});
|
||||
this._adminDialogService.openDialog('confirm', null, data, async () => {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<div *ngFor="let comment of annotation.comments" class="comment">
|
||||
<div class="comment-details-wrapper">
|
||||
<div [matTooltipPosition]="'above'" [matTooltip]="comment.date | date: 'exactCommentDate'" class="small-label">
|
||||
<div [matTooltipPosition]="'above'" [matTooltip]="comment.date | date: 'exactDate'" class="small-label">
|
||||
<b> {{ getOwnerName(comment) }} </b>
|
||||
{{ comment.date | date: 'commentDate' }}
|
||||
</div>
|
||||
|
||||
@ -38,7 +38,7 @@ export class EditDossierAttributesComponent implements EditDossierSectionInterfa
|
||||
|
||||
get changed() {
|
||||
for (const attr of this.attributes) {
|
||||
if (this.isDate(attr)) {
|
||||
if (this.isDate(attr) && attr.value) {
|
||||
if (!moment(attr.value).isSame(moment(this.currentAttrValue(attr)))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -0,0 +1,76 @@
|
||||
<iqser-table-header
|
||||
[bulkActions]="bulkActions"
|
||||
[selectionEnabled]="true"
|
||||
[tableColumnConfigs]="tableColumnConfigs"
|
||||
[tableHeaderLabel]="tableHeaderLabel"
|
||||
>
|
||||
<div
|
||||
[translateParams]="{ hours: deleteRetentionHours }"
|
||||
[translate]="'edit-dossier-dialog.deleted-documents.instructions'"
|
||||
class="instructions"
|
||||
></div>
|
||||
</iqser-table-header>
|
||||
|
||||
<redaction-empty-state
|
||||
*ngIf="entitiesService.noData$ | async"
|
||||
[text]="'edit-dossier-dialog.deleted-documents.no-data.title' | translate"
|
||||
icon="red:document"
|
||||
></redaction-empty-state>
|
||||
|
||||
<cdk-virtual-scroll-viewport *ngIf="(entitiesService.noData$ | async) === false" [itemSize]="itemSize" redactionHasScrollbar>
|
||||
<div *cdkVirtualFor="let file of sortedDisplayedEntities$ | async; trackBy: trackByPrimaryKey" class="table-item">
|
||||
<div (click)="toggleEntitySelected($event, file)" class="selection-column">
|
||||
<iqser-round-checkbox [active]="isSelected(file)"></iqser-round-checkbox>
|
||||
</div>
|
||||
<div class="filename">
|
||||
<span>{{ file.filename }}</span>
|
||||
</div>
|
||||
<div class="small-label stats-subtitle">
|
||||
<div>
|
||||
<mat-icon svgIcon="red:pages"></mat-icon>
|
||||
{{ file.numberOfPages }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="small-label">{{ file.softDeleted | date: 'exactDate' }}</div>
|
||||
<div>
|
||||
<div class="small-label">{{ file.restoreDate | date: 'timeFromNow' }}</div>
|
||||
<div class="action-buttons">
|
||||
<iqser-circle-button
|
||||
(action)="restore([file])"
|
||||
*ngIf="file.canRestore"
|
||||
[tooltip]="'edit-dossier-dialog.deleted-documents.action.restore' | translate"
|
||||
[type]="circleButtonTypes.dark"
|
||||
icon="red:put-back"
|
||||
></iqser-circle-button>
|
||||
|
||||
<iqser-circle-button
|
||||
(action)="hardDelete([file])"
|
||||
[tooltip]="'edit-dossier-dialog.deleted-documents.action.delete' | translate"
|
||||
[type]="circleButtonTypes.dark"
|
||||
icon="red:trash"
|
||||
></iqser-circle-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="scrollbar-placeholder"></div>
|
||||
</div>
|
||||
</cdk-virtual-scroll-viewport>
|
||||
|
||||
<ng-template #bulkActions>
|
||||
<div class="bulk-actions">
|
||||
<iqser-circle-button
|
||||
(action)="restore()"
|
||||
*ngIf="canRestoreSelected$ | async"
|
||||
[tooltip]="'edit-dossier-dialog.deleted-documents.bulk.restore' | translate"
|
||||
[type]="circleButtonTypes.dark"
|
||||
icon="red:put-back"
|
||||
></iqser-circle-button>
|
||||
|
||||
<iqser-circle-button
|
||||
(action)="hardDelete()"
|
||||
*ngIf="entitiesService.areSomeSelected$ | async"
|
||||
[tooltip]="'edit-dossier-dialog.deleted-documents.bulk.delete' | translate"
|
||||
[type]="circleButtonTypes.dark"
|
||||
icon="red:trash"
|
||||
></iqser-circle-button>
|
||||
</div>
|
||||
</ng-template>
|
||||
@ -0,0 +1,41 @@
|
||||
@import '../../../../../../assets/styles/variables';
|
||||
@import '../../../../../../assets/styles/red-mixins';
|
||||
|
||||
.instructions {
|
||||
color: $grey-7;
|
||||
flex: 1;
|
||||
text-align: end;
|
||||
}
|
||||
|
||||
cdk-virtual-scroll-viewport {
|
||||
height: calc(100% - 81px);
|
||||
|
||||
::ng-deep.cdk-virtual-scroll-content-wrapper {
|
||||
grid-template-columns: auto 3fr 1fr 2fr 2fr 11px;
|
||||
|
||||
.table-item > div {
|
||||
height: 50px;
|
||||
|
||||
&.filename span {
|
||||
@include line-clamp(1);
|
||||
}
|
||||
|
||||
&.stats-subtitle > div {
|
||||
width: fit-content;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.has-scrollbar:hover ::ng-deep.cdk-virtual-scroll-content-wrapper {
|
||||
grid-template-columns: auto 3fr 1fr 2fr 2fr;
|
||||
}
|
||||
}
|
||||
|
||||
.bulk-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
> *:not(:last-child) {
|
||||
margin-right: 2px;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,141 @@
|
||||
import { Component, EventEmitter, Injector, Input, OnInit, Output } from '@angular/core';
|
||||
import { EditDossierSectionInterface } from '../edit-dossier-section.interface';
|
||||
import { DossierWrapper } from '@state/model/dossier.wrapper';
|
||||
import { CircleButtonTypes, DefaultListingServices, ListingComponent, TableColumnConfig } from '@iqser/common-ui';
|
||||
import { FileManagementControllerService, FileStatus, StatusControllerService } from '@redaction/red-ui-http';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { LoadingService } from '@services/loading.service';
|
||||
import * as moment from 'moment';
|
||||
import { AppConfigKey, AppConfigService } from '@app-config/app-config.service';
|
||||
import { getLeftDateTime } from '@utils/functions';
|
||||
import { Observable } from 'rxjs';
|
||||
import { distinctUntilChanged, map } from 'rxjs/operators';
|
||||
import { ConfirmationDialogInput, TitleColors } from '@shared/dialogs/confirmation-dialog/confirmation-dialog.component';
|
||||
import { DossiersDialogService } from '../../../services/dossiers-dialog.service';
|
||||
import { AppStateService } from '@state/app-state.service';
|
||||
|
||||
interface FileListItem extends FileStatus {
|
||||
readonly canRestore: boolean;
|
||||
readonly restoreDate: string;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-edit-dossier-deleted-documents',
|
||||
templateUrl: './edit-dossier-deleted-documents.component.html',
|
||||
styleUrls: ['./edit-dossier-deleted-documents.component.scss'],
|
||||
providers: [...DefaultListingServices]
|
||||
})
|
||||
export class EditDossierDeletedDocumentsComponent extends ListingComponent<FileListItem> implements EditDossierSectionInterface, OnInit {
|
||||
@Input() dossierWrapper: DossierWrapper;
|
||||
@Output() updateDossier = new EventEmitter<any>();
|
||||
readonly changed = false;
|
||||
readonly canRestoreSelected$ = this._canRestoreSelected$;
|
||||
disabled: boolean;
|
||||
readonly tableColumnConfigs: TableColumnConfig<FileListItem>[] = [
|
||||
{ label: _('edit-dossier-dialog.deleted-documents.table-col-names.name') },
|
||||
{ label: _('edit-dossier-dialog.deleted-documents.table-col-names.pages') },
|
||||
{ label: _('edit-dossier-dialog.deleted-documents.table-col-names.deleted-on') },
|
||||
{ label: _('edit-dossier-dialog.deleted-documents.table-col-names.time-to-restore') }
|
||||
];
|
||||
readonly tableHeaderLabel = _('edit-dossier-dialog.deleted-documents.table-header.label');
|
||||
readonly itemSize = 50;
|
||||
readonly circleButtonTypes = CircleButtonTypes;
|
||||
readonly deleteRetentionHours = this._appConfigService.getConfig(AppConfigKey.DELETE_RETENTION_HOURS);
|
||||
protected readonly _primaryKey = 'fileId';
|
||||
|
||||
constructor(
|
||||
protected readonly _injector: Injector,
|
||||
private readonly _statusController: StatusControllerService,
|
||||
private readonly _fileManagementController: FileManagementControllerService,
|
||||
private readonly _appStateService: AppStateService,
|
||||
private readonly _loadingService: LoadingService,
|
||||
private readonly _appConfigService: AppConfigService,
|
||||
private readonly _dialogService: DossiersDialogService
|
||||
) {
|
||||
super(_injector);
|
||||
}
|
||||
|
||||
private get _canRestoreSelected$(): Observable<boolean> {
|
||||
return this.entitiesService.selected$.pipe(
|
||||
map(entities => entities.length && !entities.find(file => !file.canRestore)),
|
||||
distinctUntilChanged()
|
||||
);
|
||||
}
|
||||
|
||||
hardDelete(files = this.entitiesService.selected) {
|
||||
const data = new ConfirmationDialogInput({
|
||||
title: _('confirmation-dialog.permanently-delete-file.title'),
|
||||
titleColor: TitleColors.PRIMARY,
|
||||
question: _('confirmation-dialog.permanently-delete-file.question'),
|
||||
confirmationText: _('confirmation-dialog.permanently-delete-file.confirmation-text'),
|
||||
requireInput: true,
|
||||
denyText: _('confirmation-dialog.permanently-delete-file.deny-text'),
|
||||
translateParams: {
|
||||
fileName: files[0].filename,
|
||||
filesCount: files.length
|
||||
}
|
||||
});
|
||||
this._dialogService.openDialog('confirm', null, data, async () => {
|
||||
this._loadingService.loadWhile(this._hardDelete(files));
|
||||
});
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
this._loadingService.start();
|
||||
const files = await this._statusController.getDeletedFileStatus(this.dossierWrapper.dossierId).toPromise();
|
||||
this.entitiesService.setEntities(this._toListItems(files));
|
||||
this._loadingService.stop();
|
||||
}
|
||||
|
||||
revert() {}
|
||||
|
||||
save() {}
|
||||
|
||||
restore(files = this.entitiesService.selected) {
|
||||
this._loadingService.loadWhile(this._restore(files));
|
||||
}
|
||||
|
||||
private async _restore(files: FileListItem[]): Promise<void> {
|
||||
const fileIds = files.map(f => f.fileId);
|
||||
await this._fileManagementController.restoreFiles(fileIds, this.dossierWrapper.dossierId).toPromise();
|
||||
this._removeFromList(fileIds);
|
||||
await this._appStateService.reloadActiveDossierFiles();
|
||||
this.updateDossier.emit();
|
||||
}
|
||||
|
||||
private async _hardDelete(files: FileListItem[]) {
|
||||
const fileIds = files.map(f => f.fileId);
|
||||
await this._fileManagementController.hardDeleteFile(this.dossierWrapper.dossierId, fileIds).toPromise();
|
||||
this._removeFromList(fileIds);
|
||||
this.updateDossier.emit();
|
||||
}
|
||||
|
||||
private _removeFromList(ids: string[]): void {
|
||||
const entities = this.entitiesService.all.filter(e => !ids.includes(e.fileId));
|
||||
this.entitiesService.setEntities(entities);
|
||||
this.entitiesService.setSelected([]);
|
||||
}
|
||||
|
||||
private _toListItems(files: FileStatus[]): FileListItem[] {
|
||||
return files.map(file => this._toListItem(file));
|
||||
}
|
||||
|
||||
private _toListItem(file: FileStatus): FileListItem {
|
||||
const restoreDate = this._getRestoreDate(file.softDeleted);
|
||||
return {
|
||||
...file,
|
||||
restoreDate,
|
||||
canRestore: this._canRestoreFile(restoreDate)
|
||||
};
|
||||
}
|
||||
|
||||
private _canRestoreFile(restoreDate: string): boolean {
|
||||
const { daysLeft, hoursLeft, minutesLeft } = getLeftDateTime(restoreDate);
|
||||
|
||||
return daysLeft >= 0 && hoursLeft >= 0 && minutesLeft > 0;
|
||||
}
|
||||
|
||||
private _getRestoreDate(softDeletedTime: string): string {
|
||||
return moment(softDeletedTime).add(this.deleteRetentionHours, 'hours').toISOString();
|
||||
}
|
||||
}
|
||||
@ -14,7 +14,7 @@
|
||||
></div>
|
||||
</redaction-side-nav>
|
||||
<div>
|
||||
<div [class.no-padding]="noPaddingTab" class="content">
|
||||
<div [class.no-actions]="!showActionButtons" [class.no-padding]="noPaddingTab" class="content">
|
||||
<div *ngIf="showHeading" class="heading">
|
||||
{{ activeNavItem.title | translate }}
|
||||
|
||||
@ -58,9 +58,15 @@
|
||||
*ngIf="activeNav === 'dossierAttributes'"
|
||||
[dossierWrapper]="dossierWrapper"
|
||||
></redaction-edit-dossier-attributes>
|
||||
|
||||
<redaction-edit-dossier-deleted-documents
|
||||
(updateDossier)="updatedDossier($event)"
|
||||
*ngIf="activeNav === 'deletedDocuments'"
|
||||
[dossierWrapper]="dossierWrapper"
|
||||
></redaction-edit-dossier-deleted-documents>
|
||||
</div>
|
||||
|
||||
<div class="dialog-actions">
|
||||
<div *ngIf="showActionButtons" class="dialog-actions">
|
||||
<button
|
||||
(click)="save()"
|
||||
[disabled]="activeComponent?.disabled || !activeComponent?.changed"
|
||||
|
||||
@ -22,6 +22,10 @@
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
&.no-actions {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
::ng-deep .heading {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
@ -10,15 +10,16 @@ import { EditDossierTeamMembersComponent } from './team-members/edit-dossier-tea
|
||||
import { EditDossierAttributesComponent } from './attributes/edit-dossier-attributes.component';
|
||||
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { EditDossierDeletedDocumentsComponent } from './deleted-documents/edit-dossier-deleted-documents.component';
|
||||
|
||||
type Section = 'dossierInfo' | 'downloadPackage' | 'dossierDictionary' | 'members' | 'dossierAttributes';
|
||||
type Section = 'dossierInfo' | 'downloadPackage' | 'dossierDictionary' | 'members' | 'dossierAttributes' | 'deletedDocuments';
|
||||
|
||||
@Component({
|
||||
templateUrl: './edit-dossier-dialog.component.html',
|
||||
styleUrls: ['./edit-dossier-dialog.component.scss']
|
||||
})
|
||||
export class EditDossierDialogComponent {
|
||||
readonly navItems: { key: Section; title: string; sideNavTitle?: string }[];
|
||||
readonly navItems: { key: Section; title?: string; sideNavTitle?: string }[];
|
||||
activeNav: Section;
|
||||
dossierWrapper: DossierWrapper;
|
||||
|
||||
@ -27,6 +28,7 @@ export class EditDossierDialogComponent {
|
||||
@ViewChild(EditDossierDictionaryComponent) dictionaryComponent: EditDossierDictionaryComponent;
|
||||
@ViewChild(EditDossierTeamMembersComponent) membersComponent: EditDossierTeamMembersComponent;
|
||||
@ViewChild(EditDossierAttributesComponent) attributesComponent: EditDossierAttributesComponent;
|
||||
@ViewChild(EditDossierDeletedDocumentsComponent) deletedDocumentsComponent: EditDossierDeletedDocumentsComponent;
|
||||
|
||||
constructor(
|
||||
private readonly _toaster: Toaster,
|
||||
@ -62,6 +64,10 @@ export class EditDossierDialogComponent {
|
||||
{
|
||||
key: 'dossierAttributes',
|
||||
title: _('edit-dossier-dialog.nav-items.dossier-attributes')
|
||||
},
|
||||
{
|
||||
key: 'deletedDocuments',
|
||||
sideNavTitle: _('edit-dossier-dialog.nav-items.deleted-documents')
|
||||
}
|
||||
];
|
||||
|
||||
@ -79,22 +85,27 @@ export class EditDossierDialogComponent {
|
||||
downloadPackage: this.downloadPackageComponent,
|
||||
dossierDictionary: this.dictionaryComponent,
|
||||
members: this.membersComponent,
|
||||
dossierAttributes: this.attributesComponent
|
||||
dossierAttributes: this.attributesComponent,
|
||||
deletedDocuments: this.deletedDocumentsComponent
|
||||
}[this.activeNav];
|
||||
}
|
||||
|
||||
get noPaddingTab(): boolean {
|
||||
return ['dossierAttributes'].includes(this.activeNav);
|
||||
return ['dossierAttributes', 'deletedDocuments'].includes(this.activeNav);
|
||||
}
|
||||
|
||||
get showHeading(): boolean {
|
||||
return !['dossierAttributes'].includes(this.activeNav);
|
||||
return !['dossierAttributes', 'deletedDocuments'].includes(this.activeNav);
|
||||
}
|
||||
|
||||
get showSubtitle(): boolean {
|
||||
return ['dossierDictionary'].includes(this.activeNav);
|
||||
}
|
||||
|
||||
get showActionButtons(): boolean {
|
||||
return !['deletedDocuments'].includes(this.activeNav);
|
||||
}
|
||||
|
||||
updatedDossier(updatedDossier: DossierWrapper) {
|
||||
this._toaster.success(_('edit-dossier-dialog.change-successful'));
|
||||
|
||||
|
||||
@ -50,6 +50,7 @@ import { EditDossierAttributesComponent } from './dialogs/edit-dossier-dialog/at
|
||||
import { DossiersService } from './services/dossiers.service';
|
||||
import { DossierDetailsStatsComponent } from './components/dossier-details-stats/dossier-details-stats.component';
|
||||
import { SearchScreenComponent } from './screens/search-screen/search-screen.component';
|
||||
import { EditDossierDeletedDocumentsComponent } from './dialogs/edit-dossier-dialog/deleted-documents/edit-dossier-deleted-documents.component';
|
||||
|
||||
const screens = [DossierListingScreenComponent, DossierOverviewScreenComponent, FilePreviewScreenComponent, SearchScreenComponent];
|
||||
|
||||
@ -91,6 +92,7 @@ const components = [
|
||||
ScrollButtonComponent,
|
||||
PageExclusionComponent,
|
||||
DossierDetailsStatsComponent,
|
||||
EditDossierDeletedDocumentsComponent,
|
||||
|
||||
...screens,
|
||||
...dialogs
|
||||
|
||||
@ -131,7 +131,21 @@ export class AnnotationActionsService {
|
||||
permissions: AnnotationPermissions.forUser(this._permissionsService.isApprover(), this._userService.currentUser, annotation)
|
||||
}));
|
||||
|
||||
const canRecategorizeImage = annotations.length === 1 && annotationPermissions[0].permissions.canRecategorizeImage;
|
||||
const canChangeLegalBasis = annotationPermissions.reduce((acc, next) => acc && next.permissions.canChangeLegalBasis, true);
|
||||
if (canChangeLegalBasis) {
|
||||
availableActions.push({
|
||||
type: 'actionButton',
|
||||
img: this._convertPath('/assets/icons/general/edit.svg'),
|
||||
title: this._translateService.instant('annotation-actions.edit-reason.label'),
|
||||
onClick: () => {
|
||||
this._ngZone.run(() => {
|
||||
this.changeLegalBasis(null, annotations, annotationsChanged);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const canRecategorizeImage = annotationPermissions.reduce((acc, next) => acc && next.permissions.canRecategorizeImage, true);
|
||||
if (canRecategorizeImage) {
|
||||
availableActions.push({
|
||||
type: 'actionButton',
|
||||
|
||||
@ -3,6 +3,7 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
|
||||
export const fileStatusTranslations: { [key in FileStatus.StatusEnum]: string } = {
|
||||
APPROVED: _('file-status.approved'),
|
||||
DELETED: _('file-status.deleted'),
|
||||
ERROR: _('file-status.error'),
|
||||
EXCLUDED: _('file-status.excluded'),
|
||||
FULLREPROCESS: _('file-status.full-reprocess'),
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
<section *ngIf="displayed" class="full-page-load-section"></section>
|
||||
<section *ngIf="displayed" class="full-page-load-spinner">
|
||||
<mat-spinner diameter="40"></mat-spinner>
|
||||
<ng-content></ng-content>
|
||||
</section>
|
||||
<ng-container *ngIf="loadingService.isLoading$ | async">
|
||||
<section class="full-page-load-section"></section>
|
||||
<section class="full-page-load-spinner">
|
||||
<mat-spinner diameter="40"></mat-spinner>
|
||||
<ng-content></ng-content>
|
||||
</section>
|
||||
</ng-container>
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||
import { LoadingService } from '@services/loading.service';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-full-page-loading-indicator',
|
||||
@ -7,5 +8,5 @@ import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class FullPageLoadingIndicatorComponent {
|
||||
@Input() displayed = false;
|
||||
constructor(readonly loadingService: LoadingService) {}
|
||||
}
|
||||
|
||||
@ -38,7 +38,7 @@ export class InitialsAvatarComponent extends AutoUnsubscribe implements OnChange
|
||||
}
|
||||
|
||||
get disabled(): boolean {
|
||||
return !this.user?.isActive;
|
||||
return this.user && !this.user?.isActive;
|
||||
}
|
||||
|
||||
private get _colorClass() {
|
||||
|
||||
@ -48,8 +48,7 @@ export class ConfirmationDialogComponent {
|
||||
) {
|
||||
this.config = _confirmationDialogInput ?? new ConfirmationDialogInput();
|
||||
this.config = this.translate(this.config);
|
||||
this.inputLabel =
|
||||
this._translateService.instant('confirmation-dialog.delete-dossier.input-label') + ` '${this.config.confirmationText}'`;
|
||||
this.inputLabel = this._translateService.instant('confirmation-dialog.input-label') + ` '${this.config.confirmationText}'`;
|
||||
}
|
||||
|
||||
get isDeleteAction() {
|
||||
|
||||
@ -33,7 +33,7 @@ export class DatePipe extends BaseDatePipe implements PipeTransform {
|
||||
transform(value: any, format?: string, timezone?: string, locale?: string): string {
|
||||
if (format === 'timeFromNow') return this._getTimeFromNow(value);
|
||||
if (format === 'commentDate') return this._getCommentDate(value);
|
||||
if (format === 'exactCommentDate') return this._getExactCommentDate(value);
|
||||
if (format === 'exactDate') return this._getExactDate(value);
|
||||
return super.transform(value, format, timezone, locale);
|
||||
}
|
||||
|
||||
@ -87,15 +87,15 @@ export class DatePipe extends BaseDatePipe implements PipeTransform {
|
||||
return year;
|
||||
}
|
||||
|
||||
private _getExactCommentDate(item: string) {
|
||||
private _getExactDate(item: string) {
|
||||
const date = moment(item);
|
||||
const day = date.date();
|
||||
const month = this._translateService.instant(MONTH_NAMES[date.month()]);
|
||||
const year = date.year();
|
||||
const hour = date.hour();
|
||||
const minute = date.minute();
|
||||
const hour = date.hour().toString(10).padStart(2, '0');
|
||||
const minute = date.minute().toString(10).padStart(2, '0');
|
||||
|
||||
return this._translateService.instant('exact-comment-date', {
|
||||
return this._translateService.instant('exact-date', {
|
||||
day,
|
||||
month,
|
||||
year,
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
import { BehaviorSubject } 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 BehaviorSubject(false);
|
||||
private readonly _loadingEvent$ = new BehaviorSubject(false);
|
||||
readonly isLoading$ = this._loadingEvent$.asObservable();
|
||||
private _loadingStarted: number;
|
||||
|
||||
get isLoading$(): Observable<boolean> {
|
||||
return this._loadingEvent.asObservable();
|
||||
}
|
||||
|
||||
start(): void {
|
||||
this._loadingEvent.next(true);
|
||||
this._loadingStarted = new Date().getTime();
|
||||
setTimeout(() => {
|
||||
this._loadingEvent$.next(true);
|
||||
this._loadingStarted = new Date().getTime();
|
||||
});
|
||||
}
|
||||
|
||||
stop(): void {
|
||||
@ -36,7 +35,7 @@ export class LoadingService {
|
||||
}
|
||||
|
||||
private _stop() {
|
||||
setTimeout(() => this._loadingEvent.next(false));
|
||||
setTimeout(() => this._loadingEvent$.next(false));
|
||||
}
|
||||
|
||||
private _stopAfter(timeout: number) {
|
||||
|
||||
@ -1239,7 +1239,7 @@
|
||||
"no-time-left": ""
|
||||
},
|
||||
"yesterday": "",
|
||||
"exact-comment-date": "",
|
||||
"exact-date": "",
|
||||
"months": {
|
||||
"jan": "",
|
||||
"feb": "",
|
||||
|
||||
@ -365,16 +365,21 @@
|
||||
"title": "Compare with file: {fileName}"
|
||||
},
|
||||
"delete-dossier": {
|
||||
"confirmation-text": "Delete Dossier",
|
||||
"deny-text": "Keep Dossier",
|
||||
"input-label": "To proceed please type below",
|
||||
"question": "Are you sure you want to delete this dossier?",
|
||||
"title": "Delete {dossierName}",
|
||||
"title-alt": "Delete selected Dossiers"
|
||||
"confirmation-text": "Delete {dossiersCount, plural, one{Dossier} other{Dossiers}}",
|
||||
"deny-text": "Keep {dossiersCount, plural, one{Dossier} other{Dossiers}}",
|
||||
"question": "Are you sure you want to delete {dossiersCount, plural, one{this dossier} other{these dossiers}}?",
|
||||
"title": "Delete {dossiersCount, plural, one{{dossierName}} other{Selected Dossiers}}"
|
||||
},
|
||||
"delete-file": {
|
||||
"question": "Do you wish to proceed?",
|
||||
"title": "Delete Document"
|
||||
},
|
||||
"input-label": "To proceed please type below",
|
||||
"permanently-delete-file": {
|
||||
"confirmation-text": "Delete {filesCount, plural, one{Document} other{Documents}}",
|
||||
"deny-text": "Keep {filesCount, plural, one{Document} other{Documents}}",
|
||||
"question": "Are you sure you want to delete {filesCount, plural, one{this document} other{these documents}}?",
|
||||
"title": "Delete {filesCount, plural, one{{fileName}} other{Selected Documents}}"
|
||||
}
|
||||
},
|
||||
"content": "Reason",
|
||||
@ -772,6 +777,29 @@
|
||||
},
|
||||
"change-successful": "Dossier was updated.",
|
||||
"delete-successful": "Dossier was deleted.",
|
||||
"deleted-documents": {
|
||||
"action": {
|
||||
"delete": "Delete Forever",
|
||||
"restore": "Restore"
|
||||
},
|
||||
"bulk": {
|
||||
"delete": "Forever Delete Selected Documents",
|
||||
"restore": "Restore Selected Documents"
|
||||
},
|
||||
"instructions": "Deleted items can be restored up to {hours} hours from their deletion",
|
||||
"no-data": {
|
||||
"title": "There are no deleted documents."
|
||||
},
|
||||
"table-col-names": {
|
||||
"deleted-on": "Deleted On",
|
||||
"name": "Name",
|
||||
"pages": "Pages",
|
||||
"time-to-restore": "Time To Restore"
|
||||
},
|
||||
"table-header": {
|
||||
"label": "{length} deleted {length, plural, one{document} other{documents}}"
|
||||
}
|
||||
},
|
||||
"dictionary": {
|
||||
"entries": "{length} {length, plural, one{entry} other{entries}}"
|
||||
},
|
||||
@ -793,6 +821,7 @@
|
||||
"header": "Edit {dossierName}",
|
||||
"nav-items": {
|
||||
"choose-download": "Choose what is included at download:",
|
||||
"deleted-documents": "Deleted Documents",
|
||||
"dictionary": "Dictionary",
|
||||
"dossier-attributes": "Dossier Attributes",
|
||||
"dossier-dictionary": "Dossier Dictionary",
|
||||
@ -805,7 +834,7 @@
|
||||
"side-nav-title": "Configurations",
|
||||
"unsaved-changes": "You have unsaved changes. Save or revert before changing the tab."
|
||||
},
|
||||
"exact-comment-date": "{day} {month} {year} at {hour}:{minute}",
|
||||
"exact-date": "{day} {month} {year} at {hour}:{minute}",
|
||||
"file-attribute-types": {
|
||||
"date": "Date",
|
||||
"number": "Number",
|
||||
@ -963,6 +992,7 @@
|
||||
},
|
||||
"file-status": {
|
||||
"approved": "Approved",
|
||||
"deleted": "Deleted",
|
||||
"error": "Re-processing required",
|
||||
"excluded": "Excluded",
|
||||
"full-reprocess": "Processing",
|
||||
@ -984,13 +1014,13 @@
|
||||
},
|
||||
"filter": {
|
||||
"analysis": "Analysis required",
|
||||
"comment": "Comments",
|
||||
"hint": "Hints only",
|
||||
"image": "Images",
|
||||
"none": "No Annotations",
|
||||
"redaction": "Redacted",
|
||||
"suggestion": "Suggested Redaction",
|
||||
"updated": "Updated",
|
||||
"comment": "Comments"
|
||||
"updated": "Updated"
|
||||
},
|
||||
"filters": {
|
||||
"assigned-people": "Assignee(s)",
|
||||
@ -1200,7 +1230,6 @@
|
||||
},
|
||||
"reports": "Reports",
|
||||
"reports-screen": {
|
||||
"invalid-upload": "Invalid format selected for Upload! Supported formats are XLSX and DOCX",
|
||||
"description": "A short text explaining how to create report documents. It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout.",
|
||||
"descriptions": {
|
||||
"dossier-attributes": "This placeholder gets replaced with the value of the dossier attribute <code>{attribute}</code>.",
|
||||
@ -1229,6 +1258,7 @@
|
||||
},
|
||||
"document-setup-description": "A short text explaining what placeholders are and how to use them in your report template. It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout.",
|
||||
"document-setup-heading": "Document Setup",
|
||||
"invalid-upload": "Invalid format selected for Upload! Supported formats are XLSX and DOCX",
|
||||
"report-documents": "Report Documents",
|
||||
"table-header": {
|
||||
"description": "Description",
|
||||
@ -1330,7 +1360,7 @@
|
||||
},
|
||||
"trash": {
|
||||
"action": {
|
||||
"delete": "Delete forever",
|
||||
"delete": "Delete Forever",
|
||||
"restore": "Restore"
|
||||
},
|
||||
"bulk": {
|
||||
|
||||
@ -325,31 +325,45 @@ export class FileManagementControllerService {
|
||||
/**
|
||||
* Hard deletes an uploaded file.
|
||||
* None
|
||||
* @param body fileId
|
||||
* @param dossierId dossierId
|
||||
* @param fileIds fileIds
|
||||
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
|
||||
* @param reportProgress flag to report request and response progress.
|
||||
*/
|
||||
public hardDeleteFile(body: Array<string>, dossierId: string, observe?: 'body', reportProgress?: boolean): Observable<any>;
|
||||
|
||||
public hardDeleteFile(dossierId: string, fileIds: Array<string>, observe?: 'body', reportProgress?: boolean): Observable<any>;
|
||||
public hardDeleteFile(
|
||||
body: Array<string>,
|
||||
dossierId: string,
|
||||
fileIds: Array<string>,
|
||||
observe?: 'response',
|
||||
reportProgress?: boolean
|
||||
): Observable<HttpResponse<any>>;
|
||||
|
||||
public hardDeleteFile(body: Array<string>, dossierId: string, observe?: 'events', reportProgress?: boolean): Observable<HttpEvent<any>>;
|
||||
|
||||
public hardDeleteFile(body: Array<string>, dossierId: string, observe: any = 'body', reportProgress: boolean = false): Observable<any> {
|
||||
if (body === null || body === undefined) {
|
||||
throw new Error('Required parameter body was null or undefined when calling hardDeleteFile.');
|
||||
}
|
||||
|
||||
public hardDeleteFile(
|
||||
dossierId: string,
|
||||
fileIds: Array<string>,
|
||||
observe?: 'events',
|
||||
reportProgress?: boolean
|
||||
): Observable<HttpEvent<any>>;
|
||||
public hardDeleteFile(
|
||||
dossierId: string,
|
||||
fileIds: Array<string>,
|
||||
observe: any = 'body',
|
||||
reportProgress: boolean = false
|
||||
): Observable<any> {
|
||||
if (dossierId === null || dossierId === undefined) {
|
||||
throw new Error('Required parameter dossierId was null or undefined when calling hardDeleteFile.');
|
||||
}
|
||||
|
||||
if (fileIds === null || fileIds === undefined) {
|
||||
throw new Error('Required parameter fileIds was null or undefined when calling hardDeleteFile.');
|
||||
}
|
||||
|
||||
let queryParameters = new HttpParams({ encoder: new CustomHttpUrlEncodingCodec() });
|
||||
if (fileIds) {
|
||||
fileIds.forEach(element => {
|
||||
queryParameters = queryParameters.append('fileIds', <any>element);
|
||||
});
|
||||
}
|
||||
|
||||
let headers = this.defaultHeaders;
|
||||
|
||||
// authentication (RED-OAUTH) required
|
||||
@ -366,15 +380,8 @@ export class FileManagementControllerService {
|
||||
headers = headers.set('Accept', httpHeaderAcceptSelected);
|
||||
}
|
||||
|
||||
// to determine the Content-Type header
|
||||
const consumes: string[] = ['*/*'];
|
||||
const httpContentTypeSelected: string | undefined = this.configuration.selectHeaderContentType(consumes);
|
||||
if (httpContentTypeSelected !== undefined) {
|
||||
headers = headers.set('Content-Type', httpContentTypeSelected);
|
||||
}
|
||||
|
||||
return this.httpClient.request<any>('delete', `${this.basePath}/delete/hard-delete/${encodeURIComponent(String(dossierId))}`, {
|
||||
body: body,
|
||||
params: queryParameters,
|
||||
withCredentials: this.configuration.withCredentials,
|
||||
headers: headers,
|
||||
observe: observe,
|
||||
|
||||
@ -41,6 +41,53 @@ export class StatusControllerService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the deleted status for a file.
|
||||
* None
|
||||
* @param dossierId dossierId
|
||||
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
|
||||
* @param reportProgress flag to report request and response progress.
|
||||
*/
|
||||
public getDeletedFileStatus(dossierId: string, observe?: 'body', reportProgress?: boolean): Observable<Array<FileStatus>>;
|
||||
public getDeletedFileStatus(
|
||||
dossierId: string,
|
||||
observe?: 'response',
|
||||
reportProgress?: boolean
|
||||
): Observable<HttpResponse<Array<FileStatus>>>;
|
||||
public getDeletedFileStatus(dossierId: string, observe?: 'events', reportProgress?: boolean): Observable<HttpEvent<Array<FileStatus>>>;
|
||||
public getDeletedFileStatus(dossierId: string, observe: any = 'body', reportProgress: boolean = false): Observable<any> {
|
||||
if (dossierId === null || dossierId === undefined) {
|
||||
throw new Error('Required parameter dossierId was null or undefined when calling getDeletedFileStatus.');
|
||||
}
|
||||
|
||||
let headers = this.defaultHeaders;
|
||||
|
||||
// authentication (RED-OAUTH) required
|
||||
if (this.configuration.accessToken) {
|
||||
const accessToken =
|
||||
typeof this.configuration.accessToken === 'function' ? this.configuration.accessToken() : this.configuration.accessToken;
|
||||
headers = headers.set('Authorization', 'Bearer ' + accessToken);
|
||||
}
|
||||
|
||||
// to determine the Accept header
|
||||
const httpHeaderAccepts: string[] = ['application/json'];
|
||||
const httpHeaderAcceptSelected: string | undefined = this.configuration.selectHeaderAccept(httpHeaderAccepts);
|
||||
if (httpHeaderAcceptSelected !== undefined) {
|
||||
headers = headers.set('Accept', httpHeaderAcceptSelected);
|
||||
}
|
||||
|
||||
return this.httpClient.request<Array<FileStatus>>(
|
||||
'get',
|
||||
`${this.basePath}/status/softdeleted/${encodeURIComponent(String(dossierId))}`,
|
||||
{
|
||||
withCredentials: this.configuration.withCredentials,
|
||||
headers: headers,
|
||||
observe: observe,
|
||||
reportProgress: reportProgress
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the status for all files in a dossier.
|
||||
* None
|
||||
|
||||
@ -59,6 +59,10 @@ export interface FileStatus {
|
||||
* Shows if the file was excluded from analysis.
|
||||
*/
|
||||
excluded?: boolean;
|
||||
/**
|
||||
* Set of excluded pages for this file.
|
||||
*/
|
||||
excludedPages?: Array<number>;
|
||||
fileAttributes?: FileAttributes;
|
||||
/**
|
||||
* The ID of the file.
|
||||
@ -84,10 +88,22 @@ export interface FileStatus {
|
||||
* Shows if any redactions were found during the analysis.
|
||||
*/
|
||||
hasRedactions?: boolean;
|
||||
/**
|
||||
* Shows if any requests were found during the analysis.
|
||||
*/
|
||||
hasRequests?: boolean;
|
||||
/**
|
||||
* Shows if there are any Suggestions in this file.
|
||||
*/
|
||||
hasSuggestions?: boolean;
|
||||
/**
|
||||
* Shows if there is any change between the previous and current analysis.
|
||||
*/
|
||||
hasUpdates?: boolean;
|
||||
/**
|
||||
* Date and time when the files attributes was last updated.
|
||||
*/
|
||||
lastFileAttributeChange?: string;
|
||||
/**
|
||||
* Shows if this file has been OCRed by us. Last Time of OCR.
|
||||
*/
|
||||
@ -124,6 +140,10 @@ export interface FileStatus {
|
||||
* Shows which rules versions was used during the analysis.
|
||||
*/
|
||||
rulesVersion?: number;
|
||||
/**
|
||||
* Shows if the file is soft deleted.
|
||||
*/
|
||||
softDeleted?: string;
|
||||
/**
|
||||
* The status of the file with regard to its analysis an review processes.
|
||||
*/
|
||||
@ -132,40 +152,36 @@ export interface FileStatus {
|
||||
* The ID of the user who uploaded the file.
|
||||
*/
|
||||
uploader?: string;
|
||||
/**
|
||||
* The list of excluded pages.
|
||||
*/
|
||||
excludedPages?: number[];
|
||||
|
||||
hasSuggestions?: boolean;
|
||||
}
|
||||
|
||||
export namespace FileStatus {
|
||||
export type StatusEnum =
|
||||
| 'APPROVED'
|
||||
| 'DELETED'
|
||||
| 'ERROR'
|
||||
| 'EXCLUDED'
|
||||
| 'FULLREPROCESS'
|
||||
| 'INDEXING'
|
||||
| 'OCR_PROCESSING'
|
||||
| 'PROCESSING'
|
||||
| 'REPROCESS'
|
||||
| 'UNASSIGNED'
|
||||
| 'UNDER_APPROVAL'
|
||||
| 'UNDER_REVIEW'
|
||||
| 'UNPROCESSED'
|
||||
| 'INDEXING';
|
||||
| 'UNPROCESSED';
|
||||
export const StatusEnum = {
|
||||
APPROVED: 'APPROVED' as StatusEnum,
|
||||
DELETED: 'DELETED' as StatusEnum,
|
||||
ERROR: 'ERROR' as StatusEnum,
|
||||
EXCLUDED: 'EXCLUDED' as StatusEnum,
|
||||
FULLREPROCESS: 'FULLREPROCESS' as StatusEnum,
|
||||
INDEXING: 'INDEXING' as StatusEnum,
|
||||
OCRPROCESSING: 'OCR_PROCESSING' as StatusEnum,
|
||||
PROCESSING: 'PROCESSING' as StatusEnum,
|
||||
REPROCESS: 'REPROCESS' as StatusEnum,
|
||||
UNASSIGNED: 'UNASSIGNED' as StatusEnum,
|
||||
UNDERAPPROVAL: 'UNDER_APPROVAL' as StatusEnum,
|
||||
UNDERREVIEW: 'UNDER_REVIEW' as StatusEnum,
|
||||
UNPROCESSED: 'UNPROCESSED' as StatusEnum,
|
||||
INDEXING: 'INDEXING' as StatusEnum
|
||||
UNPROCESSED: 'UNPROCESSED' as StatusEnum
|
||||
} as const;
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "redaction",
|
||||
"version": "2.175.0",
|
||||
"version": "2.181.0",
|
||||
"private": true,
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user