Merge branch 'refactor' into origin/master
This commit is contained in:
commit
1da0a9b2fa
@ -7,12 +7,10 @@
|
|||||||
<div class="red-content-inner">
|
<div class="red-content-inner">
|
||||||
<div class="content-container">
|
<div class="content-container">
|
||||||
<iqser-table
|
<iqser-table
|
||||||
[actionsTemplate]="actionsTemplate"
|
|
||||||
[bulkActions]="bulkActions"
|
[bulkActions]="bulkActions"
|
||||||
[itemSize]="80"
|
[itemSize]="80"
|
||||||
[noDataText]="'downloads-list.no-data.title' | translate"
|
[noDataText]="'downloads-list.no-data.title' | translate"
|
||||||
[selectionEnabled]="true"
|
[selectionEnabled]="true"
|
||||||
emptyColumnWidth="auto"
|
|
||||||
noDataIcon="red:download"
|
noDataIcon="red:download"
|
||||||
></iqser-table>
|
></iqser-table>
|
||||||
</div>
|
</div>
|
||||||
@ -29,55 +27,49 @@
|
|||||||
></iqser-circle-button>
|
></iqser-circle-button>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
<ng-template #filenameTemplate let-download="entity">
|
<ng-template #tableItemTemplate let-download="entity">
|
||||||
<div class="cell">
|
<div>
|
||||||
<div [class.no-bold]="download.lastDownload" class="table-item-title heading">
|
<div class="cell">
|
||||||
{{ download.filename }}
|
<div [class.no-bold]="download.lastDownload" class="table-item-title heading">
|
||||||
|
{{ download.filename }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="cell">
|
||||||
|
<div class="small-label">
|
||||||
|
{{ download.size }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="cell">
|
||||||
|
<div class="small-label">
|
||||||
|
{{ download.creationDate | date: 'd MMM. yyyy, hh:mm a' }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="cell">
|
||||||
|
<div class="small-label">
|
||||||
|
{{ download.status }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="action-buttons">
|
||||||
|
<iqser-circle-button
|
||||||
|
(action)="downloadItem(download)"
|
||||||
|
*ngIf="download.status === 'READY' && !download.inProgress"
|
||||||
|
[tooltip]="'downloads-list.actions.download' | translate"
|
||||||
|
[type]="circleButtonTypes.dark"
|
||||||
|
icon="red:download"
|
||||||
|
></iqser-circle-button>
|
||||||
|
|
||||||
|
<iqser-circle-button
|
||||||
|
(action)="deleteItems([download])"
|
||||||
|
[tooltip]="'downloads-list.actions.delete' | translate"
|
||||||
|
[type]="circleButtonTypes.dark"
|
||||||
|
icon="red:trash"
|
||||||
|
></iqser-circle-button>
|
||||||
|
|
||||||
|
<mat-spinner *ngIf="download.inProgress" diameter="15"></mat-spinner>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
<ng-template #sizeTemplate let-download="entity">
|
|
||||||
<div class="cell">
|
|
||||||
<div class="small-label">
|
|
||||||
{{ download.size }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</ng-template>
|
|
||||||
|
|
||||||
<ng-template #creationDateTemplate let-download="entity">
|
|
||||||
<div class="cell">
|
|
||||||
<div class="small-label">
|
|
||||||
{{ download.creationDate | date: 'd MMM. yyyy, hh:mm a' }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</ng-template>
|
|
||||||
|
|
||||||
<ng-template #statusTemplate let-download="entity">
|
|
||||||
<div class="cell">
|
|
||||||
<div class="small-label">
|
|
||||||
{{ download.status }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</ng-template>
|
|
||||||
|
|
||||||
<ng-template #actionsTemplate let-download="entity">
|
|
||||||
<div [class.active]="download.inProgress" class="action-buttons">
|
|
||||||
<iqser-circle-button
|
|
||||||
(action)="downloadItem(download)"
|
|
||||||
*ngIf="download.status === 'READY' && !download.inProgress"
|
|
||||||
[tooltip]="'downloads-list.actions.download' | translate"
|
|
||||||
[type]="circleButtonTypes.dark"
|
|
||||||
icon="red:download"
|
|
||||||
></iqser-circle-button>
|
|
||||||
|
|
||||||
<iqser-circle-button
|
|
||||||
(action)="deleteItems([download])"
|
|
||||||
[tooltip]="'downloads-list.actions.delete' | translate"
|
|
||||||
[type]="circleButtonTypes.dark"
|
|
||||||
icon="red:trash"
|
|
||||||
></iqser-circle-button>
|
|
||||||
|
|
||||||
<mat-spinner *ngIf="download.inProgress" diameter="15"></mat-spinner>
|
|
||||||
</div>
|
|
||||||
</ng-template>
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { Component, forwardRef, Injector, OnInit, TemplateRef, ViewChild } from '@angular/core';
|
import { Component, forwardRef, Injector, OnInit } from '@angular/core';
|
||||||
import { FileDownloadService } from '@upload-download/services/file-download.service';
|
import { FileDownloadService } from '@upload-download/services/file-download.service';
|
||||||
import { DownloadStatusWrapper } from '@upload-download/model/download-status.wrapper';
|
import { DownloadStatusWrapper } from '@upload-download/model/download-status.wrapper';
|
||||||
import { DownloadControllerService } from '@redaction/red-ui-http';
|
import { DownloadControllerService } from '@redaction/red-ui-http';
|
||||||
@ -16,11 +16,12 @@ import { RouterHistoryService } from '@services/router-history.service';
|
|||||||
export class DownloadsListScreenComponent extends ListingComponent<DownloadStatusWrapper> implements OnInit {
|
export class DownloadsListScreenComponent extends ListingComponent<DownloadStatusWrapper> implements OnInit {
|
||||||
readonly circleButtonTypes = CircleButtonTypes;
|
readonly circleButtonTypes = CircleButtonTypes;
|
||||||
readonly tableHeaderLabel = _('downloads-list.table-header.title');
|
readonly tableHeaderLabel = _('downloads-list.table-header.title');
|
||||||
tableColumnConfigs: TableColumnConfig<DownloadStatusWrapper>[];
|
readonly tableColumnConfigs: TableColumnConfig<DownloadStatusWrapper>[] = [
|
||||||
@ViewChild('filenameTemplate', { static: true }) filenameTemplate: TemplateRef<never>;
|
{ label: _('downloads-list.table-col-names.name'), width: '2fr' },
|
||||||
@ViewChild('sizeTemplate', { static: true }) sizeTemplate: TemplateRef<never>;
|
{ label: _('downloads-list.table-col-names.size') },
|
||||||
@ViewChild('creationDateTemplate', { static: true }) creationDateTemplate: TemplateRef<never>;
|
{ label: _('downloads-list.table-col-names.date') },
|
||||||
@ViewChild('statusTemplate', { static: true }) statusTemplate: TemplateRef<never>;
|
{ label: _('downloads-list.table-col-names.status') }
|
||||||
|
];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected readonly _injector: Injector,
|
protected readonly _injector: Injector,
|
||||||
@ -33,7 +34,6 @@ export class DownloadsListScreenComponent extends ListingComponent<DownloadStatu
|
|||||||
}
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
this._configureTableColumns();
|
|
||||||
this._loadingService.start();
|
this._loadingService.start();
|
||||||
await this._loadData();
|
await this._loadData();
|
||||||
this.addSubscription = timer(0, 5000).subscribe(async () => {
|
this.addSubscription = timer(0, 5000).subscribe(async () => {
|
||||||
@ -50,15 +50,6 @@ export class DownloadsListScreenComponent extends ListingComponent<DownloadStatu
|
|||||||
this._loadingService.loadWhile(this._deleteItems(downloads));
|
this._loadingService.loadWhile(this._deleteItems(downloads));
|
||||||
}
|
}
|
||||||
|
|
||||||
private _configureTableColumns() {
|
|
||||||
this.tableColumnConfigs = [
|
|
||||||
{ label: _('downloads-list.table-col-names.name'), width: '2fr', template: this.filenameTemplate },
|
|
||||||
{ label: _('downloads-list.table-col-names.size'), template: this.sizeTemplate },
|
|
||||||
{ label: _('downloads-list.table-col-names.date'), template: this.creationDateTemplate },
|
|
||||||
{ label: _('downloads-list.table-col-names.status'), template: this.statusTemplate }
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _deleteItems(downloads?: DownloadStatusWrapper[]) {
|
private async _deleteItems(downloads?: DownloadStatusWrapper[]) {
|
||||||
const storageIds = (downloads || this.entitiesService.selected).map(d => d.storageId);
|
const storageIds = (downloads || this.entitiesService.selected).map(d => d.storageId);
|
||||||
await this._downloadControllerService.deleteDownload({ storageIds }).toPromise();
|
await this._downloadControllerService.deleteDownload({ storageIds }).toPromise();
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
<iqser-table
|
<iqser-table
|
||||||
[actionsTemplate]="actionsTemplate"
|
|
||||||
[bulkActions]="bulkActions"
|
[bulkActions]="bulkActions"
|
||||||
[itemMouseEnterFn]="itemMouseEnterFn"
|
[itemMouseEnterFn]="itemMouseEnterFn"
|
||||||
[itemMouseLeaveFn]="itemMouseLeaveFn"
|
[itemMouseLeaveFn]="itemMouseLeaveFn"
|
||||||
@ -54,54 +53,50 @@
|
|||||||
</ng-container>
|
</ng-container>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
<ng-template #actionsTemplate let-field="entity">
|
<ng-template #tableItemTemplate let-field="entity">
|
||||||
<div class="action-buttons">
|
<div>
|
||||||
<iqser-circle-button
|
<div class="cell">
|
||||||
(action)="field.primaryAttribute = false; toggleFieldActive.emit(field)"
|
<iqser-editable-input
|
||||||
[removeTooltip]="true"
|
(save)="field.name = $event"
|
||||||
[tooltip]="'file-attributes-csv-import.action.remove' | translate"
|
[buttonsType]="circleButtonTypes.dark"
|
||||||
[type]="circleButtonTypes.dark"
|
[cancelTooltip]="'file-attributes-csv-import.action.cancel-edit-name' | translate"
|
||||||
icon="red:trash"
|
[class]="'w-200'"
|
||||||
></iqser-circle-button>
|
[editTooltip]="'file-attributes-csv-import.action.edit-name' | translate"
|
||||||
</div>
|
[saveTooltip]="'file-attributes-csv-import.action.save-name' | translate"
|
||||||
</ng-template>
|
[value]="field.name"
|
||||||
|
></iqser-editable-input>
|
||||||
|
</div>
|
||||||
|
|
||||||
<ng-template #labelTemplate let-field="entity">
|
<div class="cell">
|
||||||
<div class="cell">
|
<div class="iqser-input-group">
|
||||||
<iqser-editable-input
|
<mat-form-field class="no-label">
|
||||||
(save)="field.name = $event"
|
<mat-select [(ngModel)]="field.type">
|
||||||
[buttonsType]="circleButtonTypes.dark"
|
<mat-option *ngFor="let type of typeOptions" [value]="type">
|
||||||
[cancelTooltip]="'file-attributes-csv-import.action.cancel-edit-name' | translate"
|
{{ translations[type] | translate }}
|
||||||
[class]="'w-200'"
|
</mat-option>
|
||||||
[editTooltip]="'file-attributes-csv-import.action.edit-name' | translate"
|
</mat-select>
|
||||||
[saveTooltip]="'file-attributes-csv-import.action.save-name' | translate"
|
</mat-form-field>
|
||||||
[value]="field.name"
|
</div>
|
||||||
></iqser-editable-input>
|
</div>
|
||||||
</div>
|
|
||||||
</ng-template>
|
|
||||||
|
|
||||||
<ng-template #typeTemplate let-field="entity">
|
<div class="cell center">
|
||||||
<div class="cell">
|
<mat-slide-toggle [(ngModel)]="field.readonly" color="primary"></mat-slide-toggle>
|
||||||
<div class="iqser-input-group">
|
</div>
|
||||||
<mat-form-field class="no-label">
|
|
||||||
<mat-select [(ngModel)]="field.type">
|
<div class="cell center">
|
||||||
<mat-option *ngFor="let type of typeOptions" [value]="type">
|
<iqser-round-checkbox (click)="togglePrimary(field)" [active]="field.primaryAttribute"></iqser-round-checkbox>
|
||||||
{{ translations[type] | translate }}
|
</div>
|
||||||
</mat-option>
|
|
||||||
</mat-select>
|
<div class="cell">
|
||||||
</mat-form-field>
|
<div class="action-buttons">
|
||||||
|
<iqser-circle-button
|
||||||
|
(action)="field.primaryAttribute = false; toggleFieldActive.emit(field)"
|
||||||
|
[removeTooltip]="true"
|
||||||
|
[tooltip]="'file-attributes-csv-import.action.remove' | translate"
|
||||||
|
[type]="circleButtonTypes.dark"
|
||||||
|
icon="red:trash"
|
||||||
|
></iqser-circle-button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
<ng-template #readonlyTemplate let-field="entity">
|
|
||||||
<div class="cell center">
|
|
||||||
<mat-slide-toggle [(ngModel)]="field.readonly" color="primary"></mat-slide-toggle>
|
|
||||||
</div>
|
|
||||||
</ng-template>
|
|
||||||
|
|
||||||
<ng-template #primaryTemplate let-field="entity">
|
|
||||||
<div class="cell center">
|
|
||||||
<iqser-round-checkbox (click)="togglePrimary(field)" [active]="field.primaryAttribute"></iqser-round-checkbox>
|
|
||||||
</div>
|
|
||||||
</ng-template>
|
|
||||||
|
|||||||
@ -30,19 +30,19 @@
|
|||||||
|
|
||||||
cdk-virtual-scroll-viewport {
|
cdk-virtual-scroll-viewport {
|
||||||
height: calc(100% - 80px) !important;
|
height: calc(100% - 80px) !important;
|
||||||
|
}
|
||||||
.cdk-virtual-scroll-content-wrapper .table-item > div.cell {
|
}
|
||||||
iqser-editable-input:not(.editing) {
|
|
||||||
padding-left: 12px;
|
.cell {
|
||||||
}
|
iqser-editable-input:not(.editing) {
|
||||||
|
padding-left: 12px;
|
||||||
iqser-editable-input::ng-deep .edit-button {
|
}
|
||||||
display: none;
|
|
||||||
}
|
iqser-editable-input::ng-deep .edit-button {
|
||||||
|
display: none;
|
||||||
&:hover iqser-editable-input::ng-deep .edit-button {
|
}
|
||||||
display: block;
|
|
||||||
}
|
&:hover iqser-editable-input::ng-deep .edit-button {
|
||||||
}
|
display: block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,16 +1,4 @@
|
|||||||
import {
|
import { Component, EventEmitter, forwardRef, Injector, Input, OnChanges, Output, SimpleChanges } from '@angular/core';
|
||||||
Component,
|
|
||||||
EventEmitter,
|
|
||||||
forwardRef,
|
|
||||||
Injector,
|
|
||||||
Input,
|
|
||||||
OnChanges,
|
|
||||||
OnInit,
|
|
||||||
Output,
|
|
||||||
SimpleChanges,
|
|
||||||
TemplateRef,
|
|
||||||
ViewChild
|
|
||||||
} from '@angular/core';
|
|
||||||
import { Field } from '../file-attributes-csv-import-dialog.component';
|
import { Field } from '../file-attributes-csv-import-dialog.component';
|
||||||
import { FileAttributeConfigTypes } from '@redaction/red-ui-http';
|
import { FileAttributeConfigTypes } from '@redaction/red-ui-http';
|
||||||
import { CircleButtonTypes, DefaultListingServices, ListingComponent, TableColumnConfig } from '@iqser/common-ui';
|
import { CircleButtonTypes, DefaultListingServices, ListingComponent, TableColumnConfig } from '@iqser/common-ui';
|
||||||
@ -23,16 +11,35 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
|||||||
styleUrls: ['./active-fields-listing.component.scss'],
|
styleUrls: ['./active-fields-listing.component.scss'],
|
||||||
providers: [...DefaultListingServices, { provide: ListingComponent, useExisting: forwardRef(() => ActiveFieldsListingComponent) }]
|
providers: [...DefaultListingServices, { provide: ListingComponent, useExisting: forwardRef(() => ActiveFieldsListingComponent) }]
|
||||||
})
|
})
|
||||||
export class ActiveFieldsListingComponent extends ListingComponent<Field> implements OnChanges, OnInit {
|
export class ActiveFieldsListingComponent extends ListingComponent<Field> implements OnChanges {
|
||||||
readonly circleButtonTypes = CircleButtonTypes;
|
readonly circleButtonTypes = CircleButtonTypes;
|
||||||
readonly translations = fileAttributeTypesTranslations;
|
readonly translations = fileAttributeTypesTranslations;
|
||||||
readonly tableHeaderLabel = _('file-attributes-csv-import.table-header.title');
|
readonly tableHeaderLabel = _('file-attributes-csv-import.table-header.title');
|
||||||
tableColumnConfigs: TableColumnConfig<Field>[];
|
readonly tableColumnConfigs: TableColumnConfig<Field>[] = [
|
||||||
|
{
|
||||||
|
label: _('file-attributes-csv-import.table-col-names.name'),
|
||||||
|
class: 'name',
|
||||||
|
width: 'minmax(0, 350px)'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: _('file-attributes-csv-import.table-col-names.type'),
|
||||||
|
width: '150px'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: _('file-attributes-csv-import.table-col-names.read-only'),
|
||||||
|
class: 'flex-center',
|
||||||
|
leftIcon: 'red:read-only',
|
||||||
|
width: 'auto'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: _('file-attributes-csv-import.table-col-names.primary'),
|
||||||
|
class: 'flex-center',
|
||||||
|
rightIcon: 'red:status-info',
|
||||||
|
rightIconTooltip: _('file-attributes-csv-import.table-col-names.primary-info-tooltip'),
|
||||||
|
width: 'auto'
|
||||||
|
}
|
||||||
|
];
|
||||||
readonly typeOptions = Object.keys(FileAttributeConfigTypes);
|
readonly typeOptions = Object.keys(FileAttributeConfigTypes);
|
||||||
@ViewChild('labelTemplate', { static: true }) labelTemplate: TemplateRef<never>;
|
|
||||||
@ViewChild('typeTemplate', { static: true }) typeTemplate: TemplateRef<never>;
|
|
||||||
@ViewChild('readonlyTemplate', { static: true }) readonlyTemplate: TemplateRef<never>;
|
|
||||||
@ViewChild('primaryTemplate', { static: true }) primaryTemplate: TemplateRef<never>;
|
|
||||||
@Input() entities: Field[];
|
@Input() entities: Field[];
|
||||||
@Output() readonly entitiesChange = new EventEmitter<Field[]>();
|
@Output() readonly entitiesChange = new EventEmitter<Field[]>();
|
||||||
@Output() readonly setHoveredColumn = new EventEmitter<string>();
|
@Output() readonly setHoveredColumn = new EventEmitter<string>();
|
||||||
@ -42,10 +49,6 @@ export class ActiveFieldsListingComponent extends ListingComponent<Field> implem
|
|||||||
super(_injector);
|
super(_injector);
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
|
||||||
this._configureTableColumns();
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnChanges(changes: SimpleChanges): void {
|
ngOnChanges(changes: SimpleChanges): void {
|
||||||
if (changes.entities) {
|
if (changes.entities) {
|
||||||
this.entitiesService.setEntities(this.entities);
|
this.entitiesService.setEntities(this.entities);
|
||||||
@ -80,35 +83,4 @@ export class ActiveFieldsListingComponent extends ListingComponent<Field> implem
|
|||||||
|
|
||||||
itemMouseEnterFn = (field: Field) => this.setHoveredColumn.emit(field.csvColumn);
|
itemMouseEnterFn = (field: Field) => this.setHoveredColumn.emit(field.csvColumn);
|
||||||
itemMouseLeaveFn = () => this.setHoveredColumn.emit();
|
itemMouseLeaveFn = () => this.setHoveredColumn.emit();
|
||||||
|
|
||||||
private _configureTableColumns() {
|
|
||||||
this.tableColumnConfigs = [
|
|
||||||
{
|
|
||||||
label: _('file-attributes-csv-import.table-col-names.name'),
|
|
||||||
class: 'name',
|
|
||||||
template: this.labelTemplate,
|
|
||||||
width: 'minmax(0, 350px)'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: _('file-attributes-csv-import.table-col-names.type'),
|
|
||||||
template: this.typeTemplate,
|
|
||||||
width: '150px'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: _('file-attributes-csv-import.table-col-names.read-only'),
|
|
||||||
class: 'flex-center',
|
|
||||||
leftIcon: 'red:read-only',
|
|
||||||
template: this.readonlyTemplate,
|
|
||||||
width: 'auto'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: _('file-attributes-csv-import.table-col-names.primary'),
|
|
||||||
class: 'flex-center',
|
|
||||||
rightIcon: 'red:status-info',
|
|
||||||
rightIconTooltip: _('file-attributes-csv-import.table-col-names.primary-info-tooltip'),
|
|
||||||
template: this.primaryTemplate,
|
|
||||||
width: 'auto'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -97,24 +97,20 @@
|
|||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
<ng-template #messageTemplate let-log="entity">
|
<ng-template #tableItemTemplate let-log="entity">
|
||||||
<div class="cell">
|
<div>
|
||||||
{{ log.message }}
|
<div class="cell">
|
||||||
|
{{ log.message }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="small-label cell">
|
||||||
|
{{ log.recordDate | date: 'd MMM. yyyy, hh:mm a' }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="user-column cell">
|
||||||
|
<redaction-initials-avatar [userId]="log.userId" [withName]="true" size="small"></redaction-initials-avatar>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div [translate]="translations[log.category]" class="cell"></div>
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
<ng-template #dateTemplate let-log="entity">
|
|
||||||
<div class="small-label cell">
|
|
||||||
{{ log.recordDate | date: 'd MMM. yyyy, hh:mm a' }}
|
|
||||||
</div>
|
|
||||||
</ng-template>
|
|
||||||
|
|
||||||
<ng-template #userTemplate let-log="entity">
|
|
||||||
<div class="user-column cell">
|
|
||||||
<redaction-initials-avatar [userId]="log.userId" [withName]="true" size="small"></redaction-initials-avatar>
|
|
||||||
</div>
|
|
||||||
</ng-template>
|
|
||||||
|
|
||||||
<ng-template #categoryTemplate let-log="entity">
|
|
||||||
<div [translate]="translations[log.category]" class="cell"></div>
|
|
||||||
</ng-template>
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { Component, forwardRef, Injector, OnDestroy, OnInit, TemplateRef, ViewChild } from '@angular/core';
|
import { Component, forwardRef, Injector, OnDestroy, OnInit } from '@angular/core';
|
||||||
import { FormBuilder, FormGroup } from '@angular/forms';
|
import { FormBuilder, FormGroup } from '@angular/forms';
|
||||||
import { AuditControllerService, AuditResponse, AuditSearchRequest, IAudit } from '@redaction/red-ui-http';
|
import { AuditControllerService, AuditResponse, AuditSearchRequest, IAudit } from '@redaction/red-ui-http';
|
||||||
import { Moment } from 'moment';
|
import { Moment } from 'moment';
|
||||||
@ -22,15 +22,16 @@ export class AuditScreenComponent extends ListingComponent<Audit> implements OnD
|
|||||||
readonly ALL_USERS = _('audit-screen.all-users');
|
readonly ALL_USERS = _('audit-screen.all-users');
|
||||||
readonly translations = auditCategoriesTranslations;
|
readonly translations = auditCategoriesTranslations;
|
||||||
readonly currentUser = this._userService.currentUser;
|
readonly currentUser = this._userService.currentUser;
|
||||||
@ViewChild('messageTemplate', { static: true }) messageTemplate: TemplateRef<unknown>;
|
|
||||||
@ViewChild('dateTemplate', { static: true }) dateTemplate: TemplateRef<unknown>;
|
|
||||||
@ViewChild('userTemplate', { static: true }) userTemplate: TemplateRef<unknown>;
|
|
||||||
@ViewChild('categoryTemplate', { static: true }) categoryTemplate: TemplateRef<unknown>;
|
|
||||||
filterForm: FormGroup;
|
filterForm: FormGroup;
|
||||||
categories: string[] = [];
|
categories: string[] = [];
|
||||||
userIds: Set<string>;
|
userIds: Set<string>;
|
||||||
logs: AuditResponse;
|
logs: AuditResponse;
|
||||||
tableColumnConfigs: TableColumnConfig<Audit>[];
|
readonly tableColumnConfigs: TableColumnConfig<Audit>[] = [
|
||||||
|
{ label: _('audit-screen.table-col-names.message') },
|
||||||
|
{ label: _('audit-screen.table-col-names.date') },
|
||||||
|
{ label: _('audit-screen.table-col-names.user'), class: 'user-column' },
|
||||||
|
{ label: _('audit-screen.table-col-names.category') }
|
||||||
|
];
|
||||||
readonly tableHeaderLabel = _('audit-screen.table-header.title');
|
readonly tableHeaderLabel = _('audit-screen.table-header.title');
|
||||||
private _previousFrom: Moment;
|
private _previousFrom: Moment;
|
||||||
private _previousTo: Moment;
|
private _previousTo: Moment;
|
||||||
@ -69,32 +70,9 @@ export class AuditScreenComponent extends ListingComponent<Audit> implements OnD
|
|||||||
}
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
this._configureTableColumns();
|
|
||||||
await this._fetchData();
|
await this._fetchData();
|
||||||
}
|
}
|
||||||
|
|
||||||
private _configureTableColumns() {
|
|
||||||
this.tableColumnConfigs = [
|
|
||||||
{
|
|
||||||
label: _('audit-screen.table-col-names.message'),
|
|
||||||
template: this.messageTemplate
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: _('audit-screen.table-col-names.date'),
|
|
||||||
template: this.dateTemplate
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: _('audit-screen.table-col-names.user'),
|
|
||||||
class: 'user-column',
|
|
||||||
template: this.userTemplate
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: _('audit-screen.table-col-names.category'),
|
|
||||||
template: this.categoryTemplate
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
private _updateDateFilters(value): boolean {
|
private _updateDateFilters(value): boolean {
|
||||||
if (applyIntervalConstraints(value, this._previousFrom, this._previousTo, this.filterForm, 'from', 'to')) {
|
if (applyIntervalConstraints(value, this._previousFrom, this._previousTo, this.filterForm, 'from', 'to')) {
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@ -20,31 +20,31 @@
|
|||||||
<redaction-admin-side-nav type="dossierTemplates"></redaction-admin-side-nav>
|
<redaction-admin-side-nav type="dossierTemplates"></redaction-admin-side-nav>
|
||||||
|
|
||||||
<div class="content-container">
|
<div class="content-container">
|
||||||
<iqser-table [actionsTemplate]="actionsTemplate" [itemSize]="80" emptyColumnWidth="2fr"></iqser-table>
|
<iqser-table [itemSize]="80" emptyColumnWidth="2fr"></iqser-table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<ng-template #nameTemplate let-color="entity">
|
<ng-template #tableItemTemplate let-color="entity">
|
||||||
<div class="cell">
|
<div>
|
||||||
<div [translate]="translations[color.key]" class="table-item-title heading"></div>
|
<div class="cell">
|
||||||
</div>
|
<div [translate]="translations[color.key]" class="table-item-title heading"></div>
|
||||||
</ng-template>
|
</div>
|
||||||
|
|
||||||
<ng-template #colorTemplate let-color="entity">
|
<div class="cell color-wrapper">
|
||||||
<div class="cell color-wrapper">
|
<div [ngStyle]="{ 'background-color': color.value }" class="color-square"></div>
|
||||||
<div [ngStyle]="{ 'background-color': color.value }" class="color-square"></div>
|
</div>
|
||||||
</div>
|
|
||||||
</ng-template>
|
|
||||||
|
|
||||||
<ng-template #actionsTemplate let-color="entity">
|
<div class="cell">
|
||||||
<div class="action-buttons">
|
<div class="action-buttons">
|
||||||
<iqser-circle-button
|
<iqser-circle-button
|
||||||
(action)="openEditColorDialog($event, color)"
|
(action)="openEditColorDialog($event, color)"
|
||||||
*ngIf="currentUser.isAdmin"
|
*ngIf="currentUser.isAdmin"
|
||||||
[tooltip]="'default-colors-screen.action.edit' | translate"
|
[tooltip]="'default-colors-screen.action.edit' | translate"
|
||||||
[type]="circleButtonTypes.dark"
|
[type]="circleButtonTypes.dark"
|
||||||
icon="iqser:edit"
|
icon="iqser:edit"
|
||||||
></iqser-circle-button>
|
></iqser-circle-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|||||||
@ -1,15 +1,9 @@
|
|||||||
:host ::ng-deep iqser-table cdk-virtual-scroll-viewport .cdk-virtual-scroll-content-wrapper .table-item > div.cell {
|
.color-wrapper {
|
||||||
&:not(.scrollbar-placeholder) {
|
align-items: center;
|
||||||
padding-left: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.color-wrapper {
|
.color-square {
|
||||||
align-items: center;
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
.color-square {
|
min-width: 16px;
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
min-width: 16px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { ChangeDetectionStrategy, Component, forwardRef, Injector, OnInit, TemplateRef, ViewChild } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, forwardRef, Injector, OnInit } from '@angular/core';
|
||||||
import { AppStateService } from '@state/app-state.service';
|
import { AppStateService } from '@state/app-state.service';
|
||||||
import { Colors } from '@redaction/red-ui-http';
|
import { Colors } from '@redaction/red-ui-http';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
@ -29,13 +29,14 @@ interface ListItem extends IListable {
|
|||||||
providers: [...DefaultListingServices, { provide: ListingComponent, useExisting: forwardRef(() => DefaultColorsScreenComponent) }]
|
providers: [...DefaultListingServices, { provide: ListingComponent, useExisting: forwardRef(() => DefaultColorsScreenComponent) }]
|
||||||
})
|
})
|
||||||
export class DefaultColorsScreenComponent extends ListingComponent<ListItem> implements OnInit {
|
export class DefaultColorsScreenComponent extends ListingComponent<ListItem> implements OnInit {
|
||||||
@ViewChild('nameTemplate', { static: true }) nameTemplate: TemplateRef<unknown>;
|
|
||||||
@ViewChild('colorTemplate', { static: true }) colorTemplate: TemplateRef<unknown>;
|
|
||||||
readonly circleButtonTypes = CircleButtonTypes;
|
readonly circleButtonTypes = CircleButtonTypes;
|
||||||
readonly currentUser = this._userService.currentUser;
|
readonly currentUser = this._userService.currentUser;
|
||||||
readonly translations = defaultColorsTranslations;
|
readonly translations = defaultColorsTranslations;
|
||||||
readonly tableHeaderLabel = _('default-colors-screen.table-header.title');
|
readonly tableHeaderLabel = _('default-colors-screen.table-header.title');
|
||||||
tableColumnConfigs: TableColumnConfig<ListItem>[];
|
readonly tableColumnConfigs: TableColumnConfig<ListItem>[] = [
|
||||||
|
{ label: _('default-colors-screen.table-col-names.key'), sortByKey: 'searchKey', width: '2fr' },
|
||||||
|
{ label: _('default-colors-screen.table-col-names.color'), class: 'flex-center' }
|
||||||
|
];
|
||||||
private _colorsObj: Colors;
|
private _colorsObj: Colors;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@ -52,7 +53,6 @@ export class DefaultColorsScreenComponent extends ListingComponent<ListItem> imp
|
|||||||
}
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
this._configureTableColumns();
|
|
||||||
await this._loadColors();
|
await this._loadColors();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,22 +72,6 @@ export class DefaultColorsScreenComponent extends ListingComponent<ListItem> imp
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _configureTableColumns() {
|
|
||||||
this.tableColumnConfigs = [
|
|
||||||
{
|
|
||||||
label: _('default-colors-screen.table-col-names.key'),
|
|
||||||
sortByKey: 'searchKey',
|
|
||||||
template: this.nameTemplate,
|
|
||||||
width: '2fr'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: _('default-colors-screen.table-col-names.color'),
|
|
||||||
class: 'flex-center',
|
|
||||||
template: this.colorTemplate
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _loadColors() {
|
private async _loadColors() {
|
||||||
this._loadingService.start();
|
this._loadingService.start();
|
||||||
const data = await this._appStateService.loadColors(this._appStateService.activeDossierTemplateId).toPromise();
|
const data = await this._appStateService.loadColors(this._appStateService.activeDossierTemplateId).toPromise();
|
||||||
|
|||||||
@ -22,7 +22,6 @@
|
|||||||
<div class="content-container">
|
<div class="content-container">
|
||||||
<iqser-table
|
<iqser-table
|
||||||
(noDataAction)="openAddEditDictionaryDialog()"
|
(noDataAction)="openAddEditDictionaryDialog()"
|
||||||
[actionsTemplate]="actionsTemplate"
|
|
||||||
[bulkActions]="bulkActions"
|
[bulkActions]="bulkActions"
|
||||||
[headerTemplate]="headerTemplate"
|
[headerTemplate]="headerTemplate"
|
||||||
[itemSize]="80"
|
[itemSize]="80"
|
||||||
@ -78,53 +77,51 @@
|
|||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
<ng-template #labelTemplate let-entity="entity">
|
<ng-template #tableItemTemplate let-dict="entity">
|
||||||
<div class="cell">
|
<div>
|
||||||
<div [ngStyle]="{ 'background-color': entity.hexColor }" class="color-square"></div>
|
<div class="cell">
|
||||||
<div class="dict-name">
|
<div [ngStyle]="{ 'background-color': dict.hexColor }" class="color-square"></div>
|
||||||
<div class="table-item-title heading">
|
<div class="dict-name">
|
||||||
{{ entity.label }}
|
<div class="table-item-title heading">
|
||||||
|
{{ dict.label }}
|
||||||
|
</div>
|
||||||
|
<div class="small-label stats-subtitle">
|
||||||
|
<div>
|
||||||
|
<mat-icon svgIcon="red:entries"></mat-icon>
|
||||||
|
{{ dict.entries?.length }}
|
||||||
|
</div>
|
||||||
|
<div *ngIf="!dict.caseInsensitive">
|
||||||
|
<mat-icon svgIcon="red:case-sensitive"></mat-icon>
|
||||||
|
{{ 'dictionary-listing.case-sensitive' | translate }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="small-label stats-subtitle">
|
</div>
|
||||||
<div>
|
|
||||||
<mat-icon svgIcon="red:entries"></mat-icon>
|
<div class="cell center small-label">
|
||||||
{{ entity.entries?.length }}
|
{{ dict.rank }}
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="!entity.caseInsensitive">
|
|
||||||
<mat-icon svgIcon="red:case-sensitive"></mat-icon>
|
<div class="cell center">
|
||||||
{{ 'dictionary-listing.case-sensitive' | translate }}
|
<redaction-annotation-icon [dictType]="dict" [type]="dict.hint ? 'circle' : 'square'"></redaction-annotation-icon>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="cell">
|
||||||
|
<div *ngIf="currentUser.isAdmin" class="action-buttons">
|
||||||
|
<iqser-circle-button
|
||||||
|
(action)="openDeleteDictionariesDialog($event, [dict])"
|
||||||
|
[tooltip]="'dictionary-listing.action.delete' | translate"
|
||||||
|
[type]="circleButtonTypes.dark"
|
||||||
|
icon="red:trash"
|
||||||
|
></iqser-circle-button>
|
||||||
|
|
||||||
|
<iqser-circle-button
|
||||||
|
(action)="openAddEditDictionaryDialog($event, dict)"
|
||||||
|
[tooltip]="'dictionary-listing.action.edit' | translate"
|
||||||
|
[type]="circleButtonTypes.dark"
|
||||||
|
icon="iqser:edit"
|
||||||
|
></iqser-circle-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
<ng-template #rankTemplate let-dict="entity">
|
|
||||||
<div class="cell center small-label">
|
|
||||||
{{ dict.rank }}
|
|
||||||
</div>
|
|
||||||
</ng-template>
|
|
||||||
|
|
||||||
<ng-template #iconTemplate let-dict="entity">
|
|
||||||
<div class="cell center">
|
|
||||||
<redaction-annotation-icon [dictType]="dict" [type]="dict.hint ? 'circle' : 'square'"></redaction-annotation-icon>
|
|
||||||
</div>
|
|
||||||
</ng-template>
|
|
||||||
|
|
||||||
<ng-template #actionsTemplate let-dict="entity">
|
|
||||||
<div *ngIf="currentUser.isAdmin" class="action-buttons">
|
|
||||||
<iqser-circle-button
|
|
||||||
(action)="openDeleteDictionariesDialog($event, [dict])"
|
|
||||||
[tooltip]="'dictionary-listing.action.delete' | translate"
|
|
||||||
[type]="circleButtonTypes.dark"
|
|
||||||
icon="red:trash"
|
|
||||||
></iqser-circle-button>
|
|
||||||
|
|
||||||
<iqser-circle-button
|
|
||||||
(action)="openAddEditDictionaryDialog($event, dict)"
|
|
||||||
[tooltip]="'dictionary-listing.action.edit' | translate"
|
|
||||||
[type]="circleButtonTypes.dark"
|
|
||||||
icon="iqser:edit"
|
|
||||||
></iqser-circle-button>
|
|
||||||
</div>
|
|
||||||
</ng-template>
|
|
||||||
|
|||||||
@ -1,39 +1,36 @@
|
|||||||
:host {
|
.cell {
|
||||||
::ng-deep iqser-table cdk-virtual-scroll-viewport .cdk-virtual-scroll-content-wrapper .table-item > div.cell {
|
flex-direction: row !important;
|
||||||
flex-direction: row;
|
align-items: center;
|
||||||
align-items: center;
|
|
||||||
justify-content: flex-start;
|
|
||||||
|
|
||||||
&.center {
|
&:not(.center) {
|
||||||
justify-content: center;
|
justify-content: flex-start !important;
|
||||||
}
|
|
||||||
|
|
||||||
.color-square {
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
min-width: 16px;
|
|
||||||
margin-right: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dict-name {
|
|
||||||
z-index: 1;
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stats-subtitle {
|
|
||||||
margin-top: 4px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.right-container {
|
.color-square {
|
||||||
display: flex;
|
width: 16px;
|
||||||
width: 353px;
|
height: 16px;
|
||||||
min-width: 353px;
|
min-width: 16px;
|
||||||
justify-content: center;
|
margin-right: 16px;
|
||||||
padding: 50px 20px 30px 20px;
|
}
|
||||||
|
|
||||||
&.has-scrollbar:hover {
|
.dict-name {
|
||||||
padding-right: 9px;
|
z-index: 1;
|
||||||
}
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats-subtitle {
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-container {
|
||||||
|
display: flex;
|
||||||
|
width: 353px;
|
||||||
|
min-width: 353px;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 50px 20px 30px 20px;
|
||||||
|
|
||||||
|
&.has-scrollbar:hover {
|
||||||
|
padding-right: 9px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { Component, forwardRef, Injector, OnInit, TemplateRef, ViewChild } from '@angular/core';
|
import { Component, forwardRef, Injector, OnInit } from '@angular/core';
|
||||||
import { DoughnutChartConfig } from '@shared/components/simple-doughnut-chart/simple-doughnut-chart.component';
|
import { DoughnutChartConfig } from '@shared/components/simple-doughnut-chart/simple-doughnut-chart.component';
|
||||||
import { AppStateService } from '@state/app-state.service';
|
import { AppStateService } from '@state/app-state.service';
|
||||||
import { catchError, defaultIfEmpty, tap } from 'rxjs/operators';
|
import { catchError, defaultIfEmpty, tap } from 'rxjs/operators';
|
||||||
@ -36,11 +36,12 @@ export class DictionaryListingScreenComponent extends ListingComponent<TypeValue
|
|||||||
readonly circleButtonTypes = CircleButtonTypes;
|
readonly circleButtonTypes = CircleButtonTypes;
|
||||||
readonly currentUser = this._userService.currentUser;
|
readonly currentUser = this._userService.currentUser;
|
||||||
readonly tableHeaderLabel = _('dictionary-listing.table-header.title');
|
readonly tableHeaderLabel = _('dictionary-listing.table-header.title');
|
||||||
tableColumnConfigs: TableColumnConfig<TypeValue>[];
|
readonly tableColumnConfigs: TableColumnConfig<TypeValue>[] = [
|
||||||
|
{ label: _('dictionary-listing.table-col-names.type'), sortByKey: 'searchKey', width: '2fr' },
|
||||||
|
{ label: _('dictionary-listing.table-col-names.order-of-importance'), sortByKey: 'rank', class: 'flex-center' },
|
||||||
|
{ label: _('dictionary-listing.table-col-names.hint-redaction'), class: 'flex-center' }
|
||||||
|
];
|
||||||
chartData: DoughnutChartConfig[] = [];
|
chartData: DoughnutChartConfig[] = [];
|
||||||
@ViewChild('labelTemplate', { static: true }) labelTemplate: TemplateRef<never>;
|
|
||||||
@ViewChild('rankTemplate', { static: true }) rankTemplate: TemplateRef<never>;
|
|
||||||
@ViewChild('iconTemplate', { static: true }) iconTemplate: TemplateRef<never>;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected readonly _injector: Injector,
|
protected readonly _injector: Injector,
|
||||||
@ -58,7 +59,6 @@ export class DictionaryListingScreenComponent extends ListingComponent<TypeValue
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this._configureTableColumns();
|
|
||||||
this._loadDictionaryData();
|
this._loadDictionaryData();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,28 +97,6 @@ export class DictionaryListingScreenComponent extends ListingComponent<TypeValue
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _configureTableColumns() {
|
|
||||||
this.tableColumnConfigs = [
|
|
||||||
{
|
|
||||||
label: _('dictionary-listing.table-col-names.type'),
|
|
||||||
sortByKey: 'searchKey',
|
|
||||||
width: '2fr',
|
|
||||||
template: this.labelTemplate
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: _('dictionary-listing.table-col-names.order-of-importance'),
|
|
||||||
sortByKey: 'rank',
|
|
||||||
class: 'flex-center',
|
|
||||||
template: this.rankTemplate
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: _('dictionary-listing.table-col-names.hint-redaction'),
|
|
||||||
class: 'flex-center',
|
|
||||||
template: this.iconTemplate
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
private _loadDictionaryData(loadEntries = true): void {
|
private _loadDictionaryData(loadEntries = true): void {
|
||||||
const appStateDictionaryData = this._appStateService.dictionaryData[this._appStateService.activeDossierTemplateId];
|
const appStateDictionaryData = this._appStateService.dictionaryData[this._appStateService.activeDossierTemplateId];
|
||||||
const entities = Object.values(appStateDictionaryData).filter(d => !d.virtual);
|
const entities = Object.values(appStateDictionaryData).filter(d => !d.virtual);
|
||||||
|
|||||||
@ -22,7 +22,6 @@
|
|||||||
<div class="content-container">
|
<div class="content-container">
|
||||||
<iqser-table
|
<iqser-table
|
||||||
(noDataAction)="openAddEditAttributeDialog(null)"
|
(noDataAction)="openAddEditAttributeDialog(null)"
|
||||||
[actionsTemplate]="actionsTemplate"
|
|
||||||
[bulkActions]="bulkActions"
|
[bulkActions]="bulkActions"
|
||||||
[headerTemplate]="headerTemplate"
|
[headerTemplate]="headerTemplate"
|
||||||
[itemSize]="50"
|
[itemSize]="50"
|
||||||
@ -65,38 +64,36 @@
|
|||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
<ng-template #actionsTemplate let-attribute="entity">
|
<ng-template #tableItemTemplate let-attribute="entity">
|
||||||
<div *ngIf="currentUser.isAdmin" class="action-buttons">
|
<div>
|
||||||
<iqser-circle-button
|
<div class="cell">
|
||||||
(action)="openAddEditAttributeDialog($event, attribute)"
|
{{ attribute.label }}
|
||||||
[tooltip]="'dossier-attributes-listing.action.edit' | translate"
|
</div>
|
||||||
[type]="circleButtonTypes.dark"
|
|
||||||
icon="iqser:edit"
|
|
||||||
></iqser-circle-button>
|
|
||||||
|
|
||||||
<iqser-circle-button
|
<div class="cell small-label">
|
||||||
(action)="openConfirmDeleteAttributeDialog($event, attribute)"
|
{{ attribute.placeholder }}
|
||||||
[tooltip]="'dossier-attributes-listing.action.delete' | translate"
|
</div>
|
||||||
[type]="circleButtonTypes.dark"
|
|
||||||
icon="red:trash"
|
<div class="cell small-label">
|
||||||
></iqser-circle-button>
|
{{ translations[attribute.type] | translate }}
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
|
||||||
|
<div class="cell">
|
||||||
<ng-template #labelTemplate let-attribute="entity">
|
<div *ngIf="currentUser.isAdmin" class="action-buttons">
|
||||||
<div class="cell">
|
<iqser-circle-button
|
||||||
{{ attribute.label }}
|
(action)="openAddEditAttributeDialog($event, attribute)"
|
||||||
</div>
|
[tooltip]="'dossier-attributes-listing.action.edit' | translate"
|
||||||
</ng-template>
|
[type]="circleButtonTypes.dark"
|
||||||
|
icon="iqser:edit"
|
||||||
<ng-template #placeholderTemplate let-attribute="entity">
|
></iqser-circle-button>
|
||||||
<div class="cell small-label">
|
|
||||||
{{ attribute.placeholder }}
|
<iqser-circle-button
|
||||||
</div>
|
(action)="openConfirmDeleteAttributeDialog($event, attribute)"
|
||||||
</ng-template>
|
[tooltip]="'dossier-attributes-listing.action.delete' | translate"
|
||||||
|
[type]="circleButtonTypes.dark"
|
||||||
<ng-template #typeTemplate let-attribute="entity">
|
icon="red:trash"
|
||||||
<div class="cell small-label">
|
></iqser-circle-button>
|
||||||
{{ translations[attribute.type] | translate }}
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { Component, forwardRef, Injector, OnInit, TemplateRef, ViewChild } from '@angular/core';
|
import { Component, forwardRef, Injector, OnInit } from '@angular/core';
|
||||||
import {
|
import {
|
||||||
CircleButtonTypes,
|
CircleButtonTypes,
|
||||||
DefaultListingServices,
|
DefaultListingServices,
|
||||||
@ -31,10 +31,11 @@ export class DossierAttributesListingScreenComponent extends ListingComponent<Do
|
|||||||
readonly currentUser = this._userService.currentUser;
|
readonly currentUser = this._userService.currentUser;
|
||||||
readonly translations = dossierAttributeTypesTranslations;
|
readonly translations = dossierAttributeTypesTranslations;
|
||||||
readonly tableHeaderLabel = _('dossier-attributes-listing.table-header.title');
|
readonly tableHeaderLabel = _('dossier-attributes-listing.table-header.title');
|
||||||
tableColumnConfigs: TableColumnConfig<DossierAttributeConfig>[];
|
readonly tableColumnConfigs: TableColumnConfig<DossierAttributeConfig>[] = [
|
||||||
@ViewChild('labelTemplate', { static: true }) labelTemplate: TemplateRef<never>;
|
{ label: _('dossier-attributes-listing.table-col-names.label'), sortByKey: 'label', width: '2fr' },
|
||||||
@ViewChild('placeholderTemplate', { static: true }) placeholderTemplate: TemplateRef<never>;
|
{ label: _('dossier-attributes-listing.table-col-names.placeholder'), width: '2fr' },
|
||||||
@ViewChild('typeTemplate', { static: true }) typeTemplate: TemplateRef<never>;
|
{ label: _('dossier-attributes-listing.table-col-names.type'), sortByKey: 'type' }
|
||||||
|
];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected readonly _injector: Injector,
|
protected readonly _injector: Injector,
|
||||||
@ -50,7 +51,6 @@ export class DossierAttributesListingScreenComponent extends ListingComponent<Do
|
|||||||
}
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
this._configureTableColumns();
|
|
||||||
await this._loadData();
|
await this._loadData();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,27 +75,6 @@ export class DossierAttributesListingScreenComponent extends ListingComponent<Do
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _configureTableColumns() {
|
|
||||||
this.tableColumnConfigs = [
|
|
||||||
{
|
|
||||||
label: _('dossier-attributes-listing.table-col-names.label'),
|
|
||||||
sortByKey: 'label',
|
|
||||||
width: '2fr',
|
|
||||||
template: this.labelTemplate
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: _('dossier-attributes-listing.table-col-names.placeholder'),
|
|
||||||
width: '2fr',
|
|
||||||
template: this.placeholderTemplate
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: _('dossier-attributes-listing.table-col-names.type'),
|
|
||||||
sortByKey: 'type',
|
|
||||||
template: this.typeTemplate
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _loadData() {
|
private async _loadData() {
|
||||||
this._loadingService.start();
|
this._loadingService.start();
|
||||||
const attributes = await this._dossierAttributesService.getConfig();
|
const attributes = await this._dossierAttributesService.getConfig();
|
||||||
|
|||||||
@ -61,37 +61,33 @@
|
|||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
<ng-template #nameTemplate let-dossierTemplate="entity">
|
<ng-template #tableItemTemplate let-dossierTemplate="entity">
|
||||||
<div class="cell">
|
<div>
|
||||||
<div class="table-item-title heading">
|
<div class="cell">
|
||||||
{{ dossierTemplate.name }}
|
<div class="table-item-title heading">
|
||||||
</div>
|
{{ dossierTemplate.name }}
|
||||||
<div class="small-label stats-subtitle">
|
</div>
|
||||||
<div>
|
<div class="small-label stats-subtitle">
|
||||||
<mat-icon svgIcon="red:dictionary"></mat-icon>
|
<div>
|
||||||
{{ 'dossier-templates-listing.dictionaries' | translate: { length: dossierTemplate.dictionariesCount } }}
|
<mat-icon svgIcon="red:dictionary"></mat-icon>
|
||||||
|
{{ 'dossier-templates-listing.dictionaries' | translate: { length: dossierTemplate.dictionariesCount } }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</ng-template>
|
|
||||||
|
|
||||||
<ng-template #userTemplate let-dossierTemplate="entity">
|
<div class="cell user-column">
|
||||||
<div class="cell user-column">
|
<redaction-initials-avatar [userId]="dossierTemplate.createdBy" [withName]="true"></redaction-initials-avatar>
|
||||||
<redaction-initials-avatar [userId]="dossierTemplate.createdBy" [withName]="true"></redaction-initials-avatar>
|
</div>
|
||||||
</div>
|
|
||||||
</ng-template>
|
<div class="cell small-label">
|
||||||
|
{{ dossierTemplate.dateAdded | date: 'd MMM. yyyy' }}
|
||||||
<ng-template #dateAddedTemplate let-dossierTemplate="entity">
|
</div>
|
||||||
<div class="cell small-label">
|
|
||||||
{{ dossierTemplate.dateAdded | date: 'd MMM. yyyy' }}
|
<div class="cell">
|
||||||
</div>
|
<div class="small-label">
|
||||||
</ng-template>
|
{{ dossierTemplate.dateModified | date: 'd MMM. yyyy' }}
|
||||||
|
</div>
|
||||||
<ng-template #dateModifiedTemplate let-dossierTemplate="entity">
|
<ng-container *ngTemplateOutlet="actionsTemplate; context: { entity: dossierTemplate }"></ng-container>
|
||||||
<div class="cell">
|
|
||||||
<div class="small-label">
|
|
||||||
{{ dossierTemplate.dateModified | date: 'd MMM. yyyy' }}
|
|
||||||
</div>
|
</div>
|
||||||
<ng-container *ngTemplateOutlet="actionsTemplate; context: { entity: dossierTemplate }"></ng-container>
|
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|||||||
@ -1,15 +1,7 @@
|
|||||||
:host {
|
.stats-subtitle {
|
||||||
::ng-deep iqser-table cdk-virtual-scroll-viewport .cdk-virtual-scroll-content-wrapper .table-item > div.cell {
|
margin-top: 4px;
|
||||||
.stats-subtitle {
|
}
|
||||||
margin-top: 4px;
|
|
||||||
}
|
.table-item-title {
|
||||||
|
max-width: 100%;
|
||||||
.table-item-title {
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-header .actions > *:not(:last-child) {
|
|
||||||
margin-right: 16px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { ChangeDetectionStrategy, Component, forwardRef, Injector, OnInit, TemplateRef, ViewChild } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, forwardRef, Injector, OnInit } from '@angular/core';
|
||||||
import { AppStateService } from '@state/app-state.service';
|
import { AppStateService } from '@state/app-state.service';
|
||||||
import { UserPreferenceService } from '@services/user-preference.service';
|
import { UserPreferenceService } from '@services/user-preference.service';
|
||||||
import { AdminDialogService } from '../../services/admin-dialog.service';
|
import { AdminDialogService } from '../../services/admin-dialog.service';
|
||||||
@ -31,11 +31,12 @@ export class DossierTemplatesListingScreenComponent extends ListingComponent<Dos
|
|||||||
readonly circleButtonTypes = CircleButtonTypes;
|
readonly circleButtonTypes = CircleButtonTypes;
|
||||||
readonly currentUser = this._userService.currentUser;
|
readonly currentUser = this._userService.currentUser;
|
||||||
readonly tableHeaderLabel = _('dossier-templates-listing.table-header.title');
|
readonly tableHeaderLabel = _('dossier-templates-listing.table-header.title');
|
||||||
tableColumnConfigs: TableColumnConfig<DossierTemplate>[];
|
readonly tableColumnConfigs: TableColumnConfig<DossierTemplate>[] = [
|
||||||
@ViewChild('nameTemplate', { static: true }) nameTemplate: TemplateRef<unknown>;
|
{ label: _('dossier-templates-listing.table-col-names.name'), sortByKey: 'searchKey' },
|
||||||
@ViewChild('userTemplate', { static: true }) userTemplate: TemplateRef<unknown>;
|
{ label: _('dossier-templates-listing.table-col-names.created-by'), class: 'user-column' },
|
||||||
@ViewChild('dateAddedTemplate', { static: true }) dateAddedTemplate: TemplateRef<unknown>;
|
{ label: _('dossier-templates-listing.table-col-names.created-on'), sortByKey: 'dateAdded' },
|
||||||
@ViewChild('dateModifiedTemplate', { static: true }) dateModifiedTemplate: TemplateRef<unknown>;
|
{ label: _('dossier-templates-listing.table-col-names.modified-on'), sortByKey: 'dateModified' }
|
||||||
|
];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected readonly _injector: Injector,
|
protected readonly _injector: Injector,
|
||||||
@ -52,7 +53,6 @@ export class DossierTemplatesListingScreenComponent extends ListingComponent<Dos
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this._configureTableColumns();
|
|
||||||
this.loadDossierTemplatesData();
|
this.loadDossierTemplatesData();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,31 +74,6 @@ export class DossierTemplatesListingScreenComponent extends ListingComponent<Dos
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private _configureTableColumns() {
|
|
||||||
this.tableColumnConfigs = [
|
|
||||||
{
|
|
||||||
label: _('dossier-templates-listing.table-col-names.name'),
|
|
||||||
sortByKey: 'searchKey',
|
|
||||||
template: this.nameTemplate
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: _('dossier-templates-listing.table-col-names.created-by'),
|
|
||||||
class: 'user-column',
|
|
||||||
template: this.userTemplate
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: _('dossier-templates-listing.table-col-names.created-on'),
|
|
||||||
sortByKey: 'dateAdded',
|
|
||||||
template: this.dateAddedTemplate
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: _('dossier-templates-listing.table-col-names.modified-on'),
|
|
||||||
sortByKey: 'dateModified',
|
|
||||||
template: this.dateModifiedTemplate
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _deleteTemplates(templateIds: string[] = this.entitiesService.selected.map(d => d.dossierTemplateId)) {
|
private async _deleteTemplates(templateIds: string[] = this.entitiesService.selected.map(d => d.dossierTemplateId)) {
|
||||||
await this._dossierTemplateControllerService
|
await this._dossierTemplateControllerService
|
||||||
.deleteDossierTemplates(templateIds)
|
.deleteDossierTemplates(templateIds)
|
||||||
|
|||||||
@ -21,7 +21,6 @@
|
|||||||
|
|
||||||
<div class="content-container">
|
<div class="content-container">
|
||||||
<iqser-table
|
<iqser-table
|
||||||
[actionsTemplate]="actionsTemplate"
|
|
||||||
[bulkActions]="bulkActions"
|
[bulkActions]="bulkActions"
|
||||||
[headerTemplate]="headerTemplate"
|
[headerTemplate]="headerTemplate"
|
||||||
[itemSize]="80"
|
[itemSize]="80"
|
||||||
@ -75,64 +74,54 @@
|
|||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
<ng-template #actionsTemplate let-attribute="entity">
|
<ng-template #tableItemTemplate let-attribute="entity">
|
||||||
<div *ngIf="currentUser.isAdmin" class="action-buttons">
|
<div>
|
||||||
<iqser-circle-button
|
<div class="label cell">
|
||||||
(action)="openAddEditAttributeDialog($event, attribute)"
|
<span>{{ attribute.label }}</span>
|
||||||
[tooltip]="'file-attributes-listing.action.edit' | translate"
|
</div>
|
||||||
[type]="circleButtonTypes.dark"
|
|
||||||
icon="iqser:edit"
|
<div [translate]="translations[attribute.type]" class="small-label cell"></div>
|
||||||
></iqser-circle-button>
|
|
||||||
<iqser-circle-button
|
<div class="center read-only cell">
|
||||||
(action)="openConfirmDeleteAttributeDialog($event, attribute)"
|
<mat-icon
|
||||||
[tooltip]="'file-attributes-listing.action.delete' | translate"
|
*ngIf="!attribute.editable"
|
||||||
[type]="circleButtonTypes.dark"
|
[matTooltip]="'file-attributes-listing.read-only' | translate"
|
||||||
icon="red:trash"
|
matTooltipPosition="above"
|
||||||
></iqser-circle-button>
|
svgIcon="red:read-only"
|
||||||
</div>
|
></mat-icon>
|
||||||
</ng-template>
|
</div>
|
||||||
|
|
||||||
<ng-template #labelTemplate let-attribute="entity">
|
<div class="small-label cell">
|
||||||
<div class="label cell">
|
{{ attribute.csvColumnHeader }}
|
||||||
<span>{{ attribute.label }}</span>
|
</div>
|
||||||
</div>
|
|
||||||
</ng-template>
|
<div class="center cell">
|
||||||
|
<iqser-round-checkbox *ngIf="attribute.filterable" [active]="true" [size]="18"></iqser-round-checkbox>
|
||||||
<ng-template #typeTemplate let-attribute="entity">
|
</div>
|
||||||
<div [translate]="translations[attribute.type]" class="small-label cell"></div>
|
|
||||||
</ng-template>
|
<div class="center cell">
|
||||||
|
<iqser-round-checkbox *ngIf="attribute.displayedInFileList" [active]="true" [size]="18"></iqser-round-checkbox>
|
||||||
<ng-template #readonlyTemplate let-attribute="entity">
|
</div>
|
||||||
<div class="center read-only cell">
|
|
||||||
<mat-icon
|
<div class="center cell">
|
||||||
*ngIf="!attribute.editable"
|
<iqser-round-checkbox *ngIf="attribute.primaryAttribute" [active]="true" [size]="18"></iqser-round-checkbox>
|
||||||
[matTooltip]="'file-attributes-listing.read-only' | translate"
|
</div>
|
||||||
matTooltipPosition="above"
|
|
||||||
svgIcon="red:read-only"
|
<div class="cell">
|
||||||
></mat-icon>
|
<div *ngIf="currentUser.isAdmin" class="action-buttons">
|
||||||
</div>
|
<iqser-circle-button
|
||||||
</ng-template>
|
(action)="openAddEditAttributeDialog($event, attribute)"
|
||||||
|
[tooltip]="'file-attributes-listing.action.edit' | translate"
|
||||||
<ng-template #csvColumnHeaderTemplate let-attribute="entity">
|
[type]="circleButtonTypes.dark"
|
||||||
<div class="small-label cell">
|
icon="iqser:edit"
|
||||||
{{ attribute.csvColumnHeader }}
|
></iqser-circle-button>
|
||||||
</div>
|
<iqser-circle-button
|
||||||
</ng-template>
|
(action)="openConfirmDeleteAttributeDialog($event, attribute)"
|
||||||
|
[tooltip]="'file-attributes-listing.action.delete' | translate"
|
||||||
<ng-template #filterableTemplate let-attribute="entity">
|
[type]="circleButtonTypes.dark"
|
||||||
<div class="center cell">
|
icon="red:trash"
|
||||||
<iqser-round-checkbox *ngIf="attribute.filterable" [active]="true" [size]="18"></iqser-round-checkbox>
|
></iqser-circle-button>
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</div>
|
||||||
|
|
||||||
<ng-template #displayedInFileListTemplate let-attribute="entity">
|
|
||||||
<div class="center cell">
|
|
||||||
<iqser-round-checkbox *ngIf="attribute.displayedInFileList" [active]="true" [size]="18"></iqser-round-checkbox>
|
|
||||||
</div>
|
|
||||||
</ng-template>
|
|
||||||
|
|
||||||
<ng-template #primaryAttributeTemplate let-attribute="entity">
|
|
||||||
<div class="center cell">
|
|
||||||
<iqser-round-checkbox *ngIf="attribute.primaryAttribute" [active]="true" [size]="18"></iqser-round-checkbox>
|
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|||||||
@ -1,10 +1,6 @@
|
|||||||
@use 'common-mixins';
|
@use 'common-mixins';
|
||||||
|
|
||||||
:host ::ng-deep iqser-table cdk-virtual-scroll-viewport .cdk-virtual-scroll-content-wrapper .table-item > div.cell {
|
.cell {
|
||||||
&.center {
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.label span {
|
&.label span {
|
||||||
@include common-mixins.line-clamp(1);
|
@include common-mixins.line-clamp(1);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,14 +1,4 @@
|
|||||||
import {
|
import { ChangeDetectionStrategy, Component, ElementRef, forwardRef, Injector, OnDestroy, OnInit, ViewChild } from '@angular/core';
|
||||||
ChangeDetectionStrategy,
|
|
||||||
Component,
|
|
||||||
ElementRef,
|
|
||||||
forwardRef,
|
|
||||||
Injector,
|
|
||||||
OnDestroy,
|
|
||||||
OnInit,
|
|
||||||
TemplateRef,
|
|
||||||
ViewChild
|
|
||||||
} from '@angular/core';
|
|
||||||
import { FileAttributesConfig, IFileAttributeConfig } from '@redaction/red-ui-http';
|
import { FileAttributesConfig, IFileAttributeConfig } from '@redaction/red-ui-http';
|
||||||
import { AppStateService } from '@state/app-state.service';
|
import { AppStateService } from '@state/app-state.service';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
@ -42,14 +32,20 @@ export class FileAttributesListingScreenComponent extends ListingComponent<FileA
|
|||||||
readonly currentUser = this._userService.currentUser;
|
readonly currentUser = this._userService.currentUser;
|
||||||
readonly translations = fileAttributeTypesTranslations;
|
readonly translations = fileAttributeTypesTranslations;
|
||||||
readonly tableHeaderLabel = _('file-attributes-listing.table-header.title');
|
readonly tableHeaderLabel = _('file-attributes-listing.table-header.title');
|
||||||
tableColumnConfigs: TableColumnConfig<FileAttributeConfig>[];
|
readonly tableColumnConfigs: TableColumnConfig<FileAttributeConfig>[] = [
|
||||||
@ViewChild('labelTemplate', { static: true }) labelTemplate: TemplateRef<unknown>;
|
{ label: _('file-attributes-listing.table-col-names.name'), sortByKey: 'searchKey', width: '2fr' },
|
||||||
@ViewChild('typeTemplate', { static: true }) typeTemplate: TemplateRef<unknown>;
|
{ label: _('file-attributes-listing.table-col-names.type'), sortByKey: 'type' },
|
||||||
@ViewChild('readonlyTemplate', { static: true }) readonlyTemplate: TemplateRef<unknown>;
|
{ label: _('file-attributes-listing.table-col-names.read-only'), sortByKey: 'editable', class: 'flex-center' },
|
||||||
@ViewChild('csvColumnHeaderTemplate', { static: true }) csvColumnHeaderTemplate: TemplateRef<unknown>;
|
{ label: _('file-attributes-listing.table-col-names.csv-column') },
|
||||||
@ViewChild('filterableTemplate', { static: true }) filterableTemplate: TemplateRef<unknown>;
|
{ label: _('file-attributes-listing.table-col-names.filterable'), class: 'flex-center' },
|
||||||
@ViewChild('displayedInFileListTemplate', { static: true }) displayedInFileListTemplate: TemplateRef<unknown>;
|
{ label: _('file-attributes-listing.table-col-names.displayed-in-file-list'), class: 'flex-center' },
|
||||||
@ViewChild('primaryAttributeTemplate', { static: true }) primaryAttributeTemplate: TemplateRef<unknown>;
|
{
|
||||||
|
label: _('file-attributes-listing.table-col-names.primary'),
|
||||||
|
class: 'flex-center',
|
||||||
|
rightIcon: 'red:status-info',
|
||||||
|
rightIconTooltip: _('file-attributes-listing.table-col-names.primary-info-tooltip')
|
||||||
|
}
|
||||||
|
];
|
||||||
private _existingConfiguration: FileAttributesConfig;
|
private _existingConfiguration: FileAttributesConfig;
|
||||||
@ViewChild('fileInput') private _fileInput: ElementRef;
|
@ViewChild('fileInput') private _fileInput: ElementRef;
|
||||||
|
|
||||||
@ -67,7 +63,6 @@ export class FileAttributesListingScreenComponent extends ListingComponent<FileA
|
|||||||
}
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
this._configureTableColumns();
|
|
||||||
await this._loadData();
|
await this._loadData();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,46 +118,6 @@ export class FileAttributesListingScreenComponent extends ListingComponent<FileA
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _configureTableColumns() {
|
|
||||||
this.tableColumnConfigs = [
|
|
||||||
{
|
|
||||||
label: _('file-attributes-listing.table-col-names.name'),
|
|
||||||
sortByKey: 'searchKey',
|
|
||||||
width: '2fr',
|
|
||||||
template: this.labelTemplate
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: _('file-attributes-listing.table-col-names.type'),
|
|
||||||
sortByKey: 'type',
|
|
||||||
template: this.typeTemplate
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: _('file-attributes-listing.table-col-names.read-only'),
|
|
||||||
sortByKey: 'editable',
|
|
||||||
class: 'flex-center',
|
|
||||||
template: this.readonlyTemplate
|
|
||||||
},
|
|
||||||
{ label: _('file-attributes-listing.table-col-names.csv-column'), template: this.csvColumnHeaderTemplate },
|
|
||||||
{
|
|
||||||
label: _('file-attributes-listing.table-col-names.filterable'),
|
|
||||||
class: 'flex-center',
|
|
||||||
template: this.filterableTemplate
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: _('file-attributes-listing.table-col-names.displayed-in-file-list'),
|
|
||||||
class: 'flex-center',
|
|
||||||
template: this.displayedInFileListTemplate
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: _('file-attributes-listing.table-col-names.primary'),
|
|
||||||
class: 'flex-center',
|
|
||||||
rightIcon: 'red:status-info',
|
|
||||||
rightIconTooltip: _('file-attributes-listing.table-col-names.primary-info-tooltip'),
|
|
||||||
template: this.primaryAttributeTemplate
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _loadData() {
|
private async _loadData() {
|
||||||
this._loadingService.start();
|
this._loadingService.start();
|
||||||
|
|
||||||
|
|||||||
@ -40,62 +40,58 @@
|
|||||||
></iqser-circle-button>
|
></iqser-circle-button>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
<ng-template #filenameTemplate let-entity="entity">
|
<ng-template #tableItemTemplate let-entity="entity">
|
||||||
<div class="cell filename">
|
<div>
|
||||||
<div [matTooltip]="entity.dossierName" class="table-item-title heading" matTooltipPosition="above">
|
<div class="cell filename">
|
||||||
{{ entity.dossierName }}
|
<div [matTooltip]="entity.dossierName" class="table-item-title heading" matTooltipPosition="above">
|
||||||
|
{{ entity.dossierName }}
|
||||||
|
</div>
|
||||||
|
<div class="small-label stats-subtitle">
|
||||||
|
<div>
|
||||||
|
<mat-icon svgIcon="red:user"></mat-icon>
|
||||||
|
{{ entity.memberIds.length }}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<mat-icon svgIcon="red:calendar"></mat-icon>
|
||||||
|
{{ entity.date | date: 'mediumDate' }}
|
||||||
|
</div>
|
||||||
|
<div *ngIf="entity.dueDate">
|
||||||
|
<mat-icon svgIcon="red:lightning"></mat-icon>
|
||||||
|
{{ entity.dueDate | date: 'mediumDate' }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="small-label stats-subtitle">
|
|
||||||
<div>
|
<div class="cell user-column">
|
||||||
<mat-icon svgIcon="red:user"></mat-icon>
|
<redaction-initials-avatar [userId]="entity.ownerId" [withName]="true"></redaction-initials-avatar>
|
||||||
{{ entity.memberIds.length }}
|
</div>
|
||||||
|
|
||||||
|
<div class="cell">
|
||||||
|
<span class="small-label">
|
||||||
|
{{ entity.softDeletedTime | date: 'd MMM. yyyy, hh:mm a' }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="cell">
|
||||||
|
<div class="small-label">
|
||||||
|
{{ entity.restoreDate | date: 'timeFromNow' }}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div class="action-buttons">
|
||||||
<mat-icon svgIcon="red:calendar"></mat-icon>
|
<iqser-circle-button
|
||||||
{{ entity.date | date: 'mediumDate' }}
|
(action)="restore([entity])"
|
||||||
</div>
|
*ngIf="entity.canRestore"
|
||||||
<div *ngIf="entity.dueDate">
|
[tooltip]="'trash.action.restore' | translate"
|
||||||
<mat-icon svgIcon="red:lightning"></mat-icon>
|
[type]="circleButtonTypes.dark"
|
||||||
{{ entity.dueDate | date: 'mediumDate' }}
|
icon="red:put-back"
|
||||||
|
></iqser-circle-button>
|
||||||
|
|
||||||
|
<iqser-circle-button
|
||||||
|
(action)="hardDelete([entity])"
|
||||||
|
[tooltip]="'trash.action.delete' | translate"
|
||||||
|
[type]="circleButtonTypes.dark"
|
||||||
|
icon="red:trash"
|
||||||
|
></iqser-circle-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
<ng-template #ownerTemplate let-entity="entity">
|
|
||||||
<div class="cell user-column">
|
|
||||||
<redaction-initials-avatar [userId]="entity.ownerId" [withName]="true"></redaction-initials-avatar>
|
|
||||||
</div>
|
|
||||||
</ng-template>
|
|
||||||
|
|
||||||
<ng-template #deletedTimeTemplate let-entity="entity">
|
|
||||||
<div class="cell">
|
|
||||||
<span class="small-label">
|
|
||||||
{{ entity.softDeletedTime | date: 'd MMM. yyyy, hh:mm a' }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</ng-template>
|
|
||||||
|
|
||||||
<ng-template #restoreDateTemplate let-entity="entity">
|
|
||||||
<div class="cell">
|
|
||||||
<div class="small-label">
|
|
||||||
{{ entity.restoreDate | date: 'timeFromNow' }}
|
|
||||||
</div>
|
|
||||||
<div class="action-buttons">
|
|
||||||
<iqser-circle-button
|
|
||||||
(action)="restore([entity])"
|
|
||||||
*ngIf="entity.canRestore"
|
|
||||||
[tooltip]="'trash.action.restore' | translate"
|
|
||||||
[type]="circleButtonTypes.dark"
|
|
||||||
icon="red:put-back"
|
|
||||||
></iqser-circle-button>
|
|
||||||
|
|
||||||
<iqser-circle-button
|
|
||||||
(action)="hardDelete([entity])"
|
|
||||||
[tooltip]="'trash.action.delete' | translate"
|
|
||||||
[type]="circleButtonTypes.dark"
|
|
||||||
icon="red:trash"
|
|
||||||
></iqser-circle-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</ng-template>
|
|
||||||
|
|||||||
@ -0,0 +1,3 @@
|
|||||||
|
.stats-subtitle {
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import { ChangeDetectionStrategy, Component, forwardRef, Injector, OnInit, TemplateRef, ViewChild } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, forwardRef, Injector, OnInit } from '@angular/core';
|
||||||
import { IDossier } from '@redaction/red-ui-http';
|
import { IDossier } from '@redaction/red-ui-http';
|
||||||
import {
|
import {
|
||||||
CircleButtonTypes,
|
CircleButtonTypes,
|
||||||
@ -35,11 +35,12 @@ export class TrashScreenComponent extends ListingComponent<DossierListItem> impl
|
|||||||
readonly circleButtonTypes = CircleButtonTypes;
|
readonly circleButtonTypes = CircleButtonTypes;
|
||||||
readonly tableHeaderLabel = _('trash.table-header.title');
|
readonly tableHeaderLabel = _('trash.table-header.title');
|
||||||
readonly canRestoreSelected$ = this._canRestoreSelected$;
|
readonly canRestoreSelected$ = this._canRestoreSelected$;
|
||||||
tableColumnConfigs: TableColumnConfig<DossierListItem>[];
|
readonly tableColumnConfigs: TableColumnConfig<DossierListItem>[] = [
|
||||||
@ViewChild('filenameTemplate', { static: true }) filenameTemplate: TemplateRef<never>;
|
{ label: _('trash.table-col-names.name'), sortByKey: 'searchKey' },
|
||||||
@ViewChild('ownerTemplate', { static: true }) ownerTemplate: TemplateRef<never>;
|
{ label: _('trash.table-col-names.owner'), class: 'user-column' },
|
||||||
@ViewChild('deletedTimeTemplate', { static: true }) deletedTimeTemplate: TemplateRef<never>;
|
{ label: _('trash.table-col-names.deleted-on'), sortByKey: 'softDeletedTime' },
|
||||||
@ViewChild('restoreDateTemplate', { static: true }) restoreDateTemplate: TemplateRef<never>;
|
{ label: _('trash.table-col-names.time-to-restore'), sortByKey: 'softDeletedTime' }
|
||||||
|
];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected readonly _injector: Injector,
|
protected readonly _injector: Injector,
|
||||||
@ -62,7 +63,6 @@ export class TrashScreenComponent extends ListingComponent<DossierListItem> impl
|
|||||||
disabledFn = (dossier: DossierListItem) => !dossier.canRestore;
|
disabledFn = (dossier: DossierListItem) => !dossier.canRestore;
|
||||||
|
|
||||||
async ngOnInit(): Promise<void> {
|
async ngOnInit(): Promise<void> {
|
||||||
this._configureTableColumns();
|
|
||||||
this._loadingService.start();
|
this._loadingService.start();
|
||||||
await this._loadDossiersData();
|
await this._loadDossiersData();
|
||||||
this.sortingService.setSortingOption({
|
this.sortingService.setSortingOption({
|
||||||
@ -95,31 +95,6 @@ export class TrashScreenComponent extends ListingComponent<DossierListItem> impl
|
|||||||
this._loadingService.loadWhile(this._restore(dossiers));
|
this._loadingService.loadWhile(this._restore(dossiers));
|
||||||
}
|
}
|
||||||
|
|
||||||
private _configureTableColumns() {
|
|
||||||
this.tableColumnConfigs = [
|
|
||||||
{
|
|
||||||
label: _('trash.table-col-names.name'),
|
|
||||||
sortByKey: 'searchKey',
|
|
||||||
template: this.filenameTemplate
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: _('trash.table-col-names.owner'),
|
|
||||||
class: 'user-column',
|
|
||||||
template: this.ownerTemplate
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: _('trash.table-col-names.deleted-on'),
|
|
||||||
sortByKey: 'softDeletedTime',
|
|
||||||
template: this.deletedTimeTemplate
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: _('trash.table-col-names.time-to-restore'),
|
|
||||||
sortByKey: 'softDeletedTime',
|
|
||||||
template: this.restoreDateTemplate
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
private _getRestoreDate(softDeletedTime: string): string {
|
private _getRestoreDate(softDeletedTime: string): string {
|
||||||
return moment(softDeletedTime).add(this._configService.values.DELETE_RETENTION_HOURS, 'hours').toISOString();
|
return moment(softDeletedTime).add(this._configService.values.DELETE_RETENTION_HOURS, 'hours').toISOString();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -33,7 +33,6 @@
|
|||||||
<div class="red-content-inner">
|
<div class="red-content-inner">
|
||||||
<div [class.extended]="collapsedDetails" class="content-container">
|
<div [class.extended]="collapsedDetails" class="content-container">
|
||||||
<iqser-table
|
<iqser-table
|
||||||
[actionsTemplate]="actionsTemplate"
|
|
||||||
[bulkActions]="bulkActions"
|
[bulkActions]="bulkActions"
|
||||||
[itemSize]="80"
|
[itemSize]="80"
|
||||||
[noMatchText]="'user-listing.no-match.title' | translate"
|
[noMatchText]="'user-listing.no-match.title' | translate"
|
||||||
@ -66,40 +65,36 @@
|
|||||||
></iqser-circle-button>
|
></iqser-circle-button>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
<ng-template #nameTemplate let-user="entity">
|
<ng-template #tableItemTemplate let-user="entity">
|
||||||
<div class="cell">
|
<div>
|
||||||
<redaction-initials-avatar [showYou]="true" [userId]="user.id" [withName]="true"></redaction-initials-avatar>
|
<div class="cell">
|
||||||
</div>
|
<redaction-initials-avatar [showYou]="true" [userId]="user.id" [withName]="true"></redaction-initials-avatar>
|
||||||
</ng-template>
|
</div>
|
||||||
|
|
||||||
<ng-template #emailTemplate let-user="entity">
|
<div class="small-label cell">{{ user.email || '-' }}</div>
|
||||||
<div class="small-label cell">{{ user.email || '-' }}</div>
|
|
||||||
</ng-template>
|
<div class="center cell">
|
||||||
|
<mat-slide-toggle (toggleChange)="toggleActive(user)" [checked]="user.isActive" color="primary"></mat-slide-toggle>
|
||||||
<ng-template #activeTemplate let-user="entity">
|
</div>
|
||||||
<div class="center cell">
|
|
||||||
<mat-slide-toggle (toggleChange)="toggleActive(user)" [checked]="user.isActive" color="primary"></mat-slide-toggle>
|
<div class="small-label cell">{{ getDisplayRoles(user) }}</div>
|
||||||
</div>
|
|
||||||
</ng-template>
|
<div class="cell">
|
||||||
|
<div class="action-buttons">
|
||||||
<ng-template #rolesTemplate let-user="entity">
|
<iqser-circle-button
|
||||||
<div class="small-label cell">{{ getDisplayRoles(user) }}</div>
|
(action)="openAddEditUserDialog($event, user)"
|
||||||
</ng-template>
|
[tooltip]="'user-listing.action.edit' | translate"
|
||||||
|
[type]="circleButtonTypes.dark"
|
||||||
<ng-template #actionsTemplate let-user="entity">
|
icon="iqser:edit"
|
||||||
<div class="action-buttons">
|
></iqser-circle-button>
|
||||||
<iqser-circle-button
|
<iqser-circle-button
|
||||||
(action)="openAddEditUserDialog($event, user)"
|
(action)="openDeleteUsersDialog([user], $event)"
|
||||||
[tooltip]="'user-listing.action.edit' | translate"
|
[disabled]="user.id === userService.currentUser.id"
|
||||||
[type]="circleButtonTypes.dark"
|
[tooltip]="'user-listing.action.delete' | translate"
|
||||||
icon="iqser:edit"
|
[type]="circleButtonTypes.dark"
|
||||||
></iqser-circle-button>
|
icon="red:trash"
|
||||||
<iqser-circle-button
|
></iqser-circle-button>
|
||||||
(action)="openDeleteUsersDialog([user], $event)"
|
</div>
|
||||||
[disabled]="user.id === userService.currentUser.id"
|
</div>
|
||||||
[tooltip]="'user-listing.action.delete' | translate"
|
|
||||||
[type]="circleButtonTypes.dark"
|
|
||||||
icon="red:trash"
|
|
||||||
></iqser-circle-button>
|
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|||||||
@ -1,7 +1,3 @@
|
|||||||
:host ::ng-deep iqser-table cdk-virtual-scroll-viewport .cdk-virtual-scroll-content-wrapper .table-item > div.cell.center {
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.right-container {
|
.right-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 353px;
|
width: 353px;
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { Component, forwardRef, Injector, OnInit, QueryList, TemplateRef, ViewChild, ViewChildren } from '@angular/core';
|
import { Component, forwardRef, Injector, OnInit, QueryList, ViewChildren } from '@angular/core';
|
||||||
import { UserService } from '@services/user.service';
|
import { UserService } from '@services/user.service';
|
||||||
import { UserControllerService } from '@redaction/red-ui-http';
|
import { UserControllerService } from '@redaction/red-ui-http';
|
||||||
import { AdminDialogService } from '../../services/admin-dialog.service';
|
import { AdminDialogService } from '../../services/admin-dialog.service';
|
||||||
@ -32,13 +32,14 @@ export class UserListingScreenComponent extends ListingComponent<User> implement
|
|||||||
readonly currentUser = this.userService.currentUser;
|
readonly currentUser = this.userService.currentUser;
|
||||||
readonly canDeleteSelected$ = this._canDeleteSelected$;
|
readonly canDeleteSelected$ = this._canDeleteSelected$;
|
||||||
readonly tableHeaderLabel = _('user-listing.table-header.title');
|
readonly tableHeaderLabel = _('user-listing.table-header.title');
|
||||||
tableColumnConfigs: TableColumnConfig<User>[];
|
readonly tableColumnConfigs: TableColumnConfig<User>[] = [
|
||||||
|
{ label: _('user-listing.table-col-names.name'), width: '2fr' },
|
||||||
|
{ label: _('user-listing.table-col-names.email') },
|
||||||
|
{ label: _('user-listing.table-col-names.active'), class: 'flex-center' },
|
||||||
|
{ label: _('user-listing.table-col-names.roles') }
|
||||||
|
];
|
||||||
collapsedDetails = false;
|
collapsedDetails = false;
|
||||||
chartData: DoughnutChartConfig[] = [];
|
chartData: DoughnutChartConfig[] = [];
|
||||||
@ViewChild('nameTemplate', { static: true }) nameTemplate: TemplateRef<never>;
|
|
||||||
@ViewChild('emailTemplate', { static: true }) emailTemplate: TemplateRef<never>;
|
|
||||||
@ViewChild('activeTemplate', { static: true }) activeTemplate: TemplateRef<never>;
|
|
||||||
@ViewChild('rolesTemplate', { static: true }) rolesTemplate: TemplateRef<never>;
|
|
||||||
@ViewChildren(InitialsAvatarComponent)
|
@ViewChildren(InitialsAvatarComponent)
|
||||||
private readonly _avatars: QueryList<InitialsAvatarComponent>;
|
private readonly _avatars: QueryList<InitialsAvatarComponent>;
|
||||||
|
|
||||||
@ -60,7 +61,6 @@ export class UserListingScreenComponent extends ListingComponent<User> implement
|
|||||||
}
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
this._configureTableColumns();
|
|
||||||
await this._loadData();
|
await this._loadData();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,15 +96,6 @@ export class UserListingScreenComponent extends ListingComponent<User> implement
|
|||||||
this.openDeleteUsersDialog(this.entitiesService.all.filter(u => this.isSelected(u)));
|
this.openDeleteUsersDialog(this.entitiesService.all.filter(u => this.isSelected(u)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private _configureTableColumns() {
|
|
||||||
this.tableColumnConfigs = [
|
|
||||||
{ label: _('user-listing.table-col-names.name'), template: this.nameTemplate, width: '2fr' },
|
|
||||||
{ label: _('user-listing.table-col-names.email'), template: this.emailTemplate },
|
|
||||||
{ label: _('user-listing.table-col-names.active'), class: 'flex-center', template: this.activeTemplate },
|
|
||||||
{ label: _('user-listing.table-col-names.roles'), template: this.rolesTemplate }
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _loadData() {
|
private async _loadData() {
|
||||||
this.entitiesService.setEntities(await this.userService.loadAllUsers());
|
this.entitiesService.setEntities(await this.userService.loadAllUsers());
|
||||||
await this.userService.loadAllUsers();
|
await this.userService.loadAllUsers();
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { AppStateService } from '@state/app-state.service';
|
|||||||
import { UserService } from '@services/user.service';
|
import { UserService } from '@services/user.service';
|
||||||
import { Toaster } from '@iqser/common-ui';
|
import { Toaster } from '@iqser/common-ui';
|
||||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||||
import { Dossier } from '../../../../state/model/dossier';
|
import { Dossier } from '@state/model/dossier';
|
||||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -119,6 +119,13 @@ export class TeamMembersManagerComponent implements OnInit {
|
|||||||
this._loadData();
|
this._loadData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setMembersSelectOptions(): void {
|
||||||
|
this.membersSelectOptions = this.userService.eligibleUsers
|
||||||
|
.filter(user => this.userService.getNameForId(user.id).toLowerCase().includes(this.searchQuery.toLowerCase()))
|
||||||
|
.filter(user => this.selectedOwnerId !== user.id)
|
||||||
|
.map(user => user.id);
|
||||||
|
}
|
||||||
|
|
||||||
private _updateChanged() {
|
private _updateChanged() {
|
||||||
if (this.dossier.ownerId !== this.selectedOwnerId) {
|
if (this.dossier.ownerId !== this.selectedOwnerId) {
|
||||||
this.changed = true;
|
this.changed = true;
|
||||||
@ -138,13 +145,6 @@ export class TeamMembersManagerComponent implements OnInit {
|
|||||||
this.selectedReviewersList = this.selectedMembersList.filter(m => this.selectedApproversList.indexOf(m) === -1);
|
this.selectedReviewersList = this.selectedMembersList.filter(m => this.selectedApproversList.indexOf(m) === -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
setMembersSelectOptions(): void {
|
|
||||||
this.membersSelectOptions = this.userService.eligibleUsers
|
|
||||||
.filter(user => this.userService.getNameForId(user.id).toLowerCase().includes(this.searchQuery.toLowerCase()))
|
|
||||||
.filter(user => this.selectedOwnerId !== user.id)
|
|
||||||
.map(user => user.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _loadData() {
|
private _loadData() {
|
||||||
this.teamForm = this._formBuilder.group({
|
this.teamForm = this._formBuilder.group({
|
||||||
owner: [this.dossier?.ownerId, Validators.required],
|
owner: [this.dossier?.ownerId, Validators.required],
|
||||||
|
|||||||
@ -69,7 +69,6 @@ export class AssignReviewerApproverDialogComponent {
|
|||||||
const selectedUser = this.selectedSingleUser;
|
const selectedUser = this.selectedSingleUser;
|
||||||
|
|
||||||
if (this.data.mode === 'reviewer') {
|
if (this.data.mode === 'reviewer') {
|
||||||
console.log('assign reviewer');
|
|
||||||
await this._filesService
|
await this._filesService
|
||||||
.setReviewerFor(
|
.setReviewerFor(
|
||||||
this.data.files.map(f => f.fileId),
|
this.data.files.map(f => f.fileId),
|
||||||
|
|||||||
@ -34,45 +34,41 @@
|
|||||||
></iqser-circle-button>
|
></iqser-circle-button>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
<ng-template #filenameTemplate let-file="entity">
|
<ng-template #tableItemTemplate let-file="entity">
|
||||||
<div class="cell filename">
|
<div>
|
||||||
<span>{{ file.filename }}</span>
|
<div class="cell filename">
|
||||||
</div>
|
<span>{{ file.filename }}</span>
|
||||||
</ng-template>
|
</div>
|
||||||
|
|
||||||
<ng-template #pagesTemplate let-file="entity">
|
<div class="cell stats-subtitle">
|
||||||
<div class="cell stats-subtitle">
|
<div class="small-label">
|
||||||
<div class="small-label">
|
<mat-icon svgIcon="red:pages"></mat-icon>
|
||||||
<mat-icon svgIcon="red:pages"></mat-icon>
|
{{ file.numberOfPages }}
|
||||||
{{ file.numberOfPages }}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</ng-template>
|
<div class="cell">
|
||||||
|
<span class="small-label">{{ file.softDeleted | date: 'exactDate' }}</span>
|
||||||
<ng-template #deletedDateTemplate let-file="entity">
|
</div>
|
||||||
<div class="cell">
|
|
||||||
<span class="small-label">{{ file.softDeleted | date: 'exactDate' }}</span>
|
<div class="cell">
|
||||||
</div>
|
<div class="small-label">{{ file.restoreDate | date: 'timeFromNow' }}</div>
|
||||||
</ng-template>
|
<div class="action-buttons">
|
||||||
|
<iqser-circle-button
|
||||||
<ng-template #restoreDateTemplate let-file="entity">
|
(action)="restore([file])"
|
||||||
<div class="cell">
|
*ngIf="file.canRestore"
|
||||||
<div class="small-label">{{ file.restoreDate | date: 'timeFromNow' }}</div>
|
[tooltip]="'edit-dossier-dialog.deleted-documents.action.restore' | translate"
|
||||||
<div class="action-buttons">
|
[type]="circleButtonTypes.dark"
|
||||||
<iqser-circle-button
|
icon="red:put-back"
|
||||||
(action)="restore([file])"
|
></iqser-circle-button>
|
||||||
*ngIf="file.canRestore"
|
|
||||||
[tooltip]="'edit-dossier-dialog.deleted-documents.action.restore' | translate"
|
<iqser-circle-button
|
||||||
[type]="circleButtonTypes.dark"
|
(action)="hardDelete([file])"
|
||||||
icon="red:put-back"
|
[tooltip]="'edit-dossier-dialog.deleted-documents.action.delete' | translate"
|
||||||
></iqser-circle-button>
|
[type]="circleButtonTypes.dark"
|
||||||
|
icon="red:trash"
|
||||||
<iqser-circle-button
|
></iqser-circle-button>
|
||||||
(action)="hardDelete([file])"
|
</div>
|
||||||
[tooltip]="'edit-dossier-dialog.deleted-documents.action.delete' | translate"
|
|
||||||
[type]="circleButtonTypes.dark"
|
|
||||||
icon="red:trash"
|
|
||||||
></iqser-circle-button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|||||||
@ -9,8 +9,8 @@
|
|||||||
|
|
||||||
:host ::ng-deep iqser-table cdk-virtual-scroll-viewport {
|
:host ::ng-deep iqser-table cdk-virtual-scroll-viewport {
|
||||||
height: calc(100% - 81px) !important;
|
height: calc(100% - 81px) !important;
|
||||||
|
}
|
||||||
.cdk-virtual-scroll-content-wrapper .table-item > div.cell.filename span {
|
|
||||||
@include common-mixins.line-clamp(1);
|
.cell.filename span {
|
||||||
}
|
@include common-mixins.line-clamp(1);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { Component, EventEmitter, forwardRef, Injector, Input, OnInit, Output, TemplateRef, ViewChild } from '@angular/core';
|
import { Component, EventEmitter, forwardRef, Injector, Input, OnInit, Output } from '@angular/core';
|
||||||
import { EditDossierSectionInterface } from '../edit-dossier-section.interface';
|
import { EditDossierSectionInterface } from '../edit-dossier-section.interface';
|
||||||
import { Dossier } from '@state/model/dossier';
|
import { Dossier } from '@state/model/dossier';
|
||||||
import {
|
import {
|
||||||
@ -42,14 +42,15 @@ export class EditDossierDeletedDocumentsComponent extends ListingComponent<FileL
|
|||||||
readonly changed = false;
|
readonly changed = false;
|
||||||
readonly canRestoreSelected$ = this._canRestoreSelected$;
|
readonly canRestoreSelected$ = this._canRestoreSelected$;
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
tableColumnConfigs: TableColumnConfig<FileListItem>[];
|
readonly tableColumnConfigs: TableColumnConfig<FileListItem>[] = [
|
||||||
|
{ label: _('edit-dossier-dialog.deleted-documents.table-col-names.name'), width: '3fr' },
|
||||||
|
{ label: _('edit-dossier-dialog.deleted-documents.table-col-names.pages') },
|
||||||
|
{ label: _('edit-dossier-dialog.deleted-documents.table-col-names.deleted-on'), sortByKey: 'softDeleted', width: '2fr' },
|
||||||
|
{ label: _('edit-dossier-dialog.deleted-documents.table-col-names.time-to-restore'), sortByKey: 'softDeleted', width: '2fr' }
|
||||||
|
];
|
||||||
readonly tableHeaderLabel = _('edit-dossier-dialog.deleted-documents.table-header.label');
|
readonly tableHeaderLabel = _('edit-dossier-dialog.deleted-documents.table-header.label');
|
||||||
readonly circleButtonTypes = CircleButtonTypes;
|
readonly circleButtonTypes = CircleButtonTypes;
|
||||||
readonly deleteRetentionHours = this._configService.values.DELETE_RETENTION_HOURS;
|
readonly deleteRetentionHours = this._configService.values.DELETE_RETENTION_HOURS;
|
||||||
@ViewChild('filenameTemplate', { static: true }) filenameTemplate: TemplateRef<never>;
|
|
||||||
@ViewChild('pagesTemplate', { static: true }) pagesTemplate: TemplateRef<never>;
|
|
||||||
@ViewChild('deletedDateTemplate', { static: true }) deletedDateTemplate: TemplateRef<never>;
|
|
||||||
@ViewChild('restoreDateTemplate', { static: true }) restoreDateTemplate: TemplateRef<never>;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected readonly _injector: Injector,
|
protected readonly _injector: Injector,
|
||||||
@ -89,7 +90,6 @@ export class EditDossierDeletedDocumentsComponent extends ListingComponent<FileL
|
|||||||
}
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
this._configureTableColumns();
|
|
||||||
this._loadingService.start();
|
this._loadingService.start();
|
||||||
const files = await this._filesService.getDeletedFilesFor(this.dossier.id).toPromise();
|
const files = await this._filesService.getDeletedFilesFor(this.dossier.id).toPromise();
|
||||||
this.entitiesService.setEntities(this._toListItems(files));
|
this.entitiesService.setEntities(this._toListItems(files));
|
||||||
@ -110,32 +110,6 @@ export class EditDossierDeletedDocumentsComponent extends ListingComponent<FileL
|
|||||||
|
|
||||||
disabledFn = (file: FileListItem) => !file.canRestore;
|
disabledFn = (file: FileListItem) => !file.canRestore;
|
||||||
|
|
||||||
private _configureTableColumns() {
|
|
||||||
this.tableColumnConfigs = [
|
|
||||||
{
|
|
||||||
label: _('edit-dossier-dialog.deleted-documents.table-col-names.name'),
|
|
||||||
template: this.filenameTemplate,
|
|
||||||
width: '3fr'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: _('edit-dossier-dialog.deleted-documents.table-col-names.pages'),
|
|
||||||
template: this.pagesTemplate
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: _('edit-dossier-dialog.deleted-documents.table-col-names.deleted-on'),
|
|
||||||
template: this.deletedDateTemplate,
|
|
||||||
sortByKey: 'softDeleted',
|
|
||||||
width: '2fr'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: _('edit-dossier-dialog.deleted-documents.table-col-names.time-to-restore'),
|
|
||||||
template: this.restoreDateTemplate,
|
|
||||||
sortByKey: 'softDeleted',
|
|
||||||
width: '2fr'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _restore(files: FileListItem[]): Promise<void> {
|
private async _restore(files: FileListItem[]): Promise<void> {
|
||||||
const fileIds = files.map(f => f.fileId);
|
const fileIds = files.map(f => f.fileId);
|
||||||
await this._fileManagementController.restoreFiles(fileIds, this.dossier.id).toPromise();
|
await this._fileManagementController.restoreFiles(fileIds, this.dossier.id).toPromise();
|
||||||
|
|||||||
@ -1,13 +1,11 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { RouterModule, Routes } from '@angular/router';
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
import { DossierListingScreenComponent } from './screens/dossier-listing-screen/dossier-listing-screen.component';
|
|
||||||
import { CompositeRouteGuard } from '@guards/composite-route.guard';
|
import { CompositeRouteGuard } from '@guards/composite-route.guard';
|
||||||
import { AuthGuard } from '../auth/auth.guard';
|
import { AuthGuard } from '../auth/auth.guard';
|
||||||
import { RedRoleGuard } from '../auth/red-role.guard';
|
import { RedRoleGuard } from '../auth/red-role.guard';
|
||||||
import { AppStateGuard } from '@state/app-state.guard';
|
import { AppStateGuard } from '@state/app-state.guard';
|
||||||
import { SearchScreenComponent } from './screens/search-screen/search-screen.component';
|
import { SearchScreenComponent } from './screens/search-screen/search-screen.component';
|
||||||
import { FilePreviewScreenComponent } from './screens/file-preview-screen/file-preview-screen.component';
|
import { FilePreviewScreenComponent } from './screens/file-preview-screen/file-preview-screen.component';
|
||||||
import { DossierOverviewScreenComponent } from './screens/dossier-overview-screen/dossier-overview-screen.component';
|
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{
|
{
|
||||||
@ -20,12 +18,12 @@ const routes: Routes = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: ':dossierId',
|
path: ':dossierId',
|
||||||
component: DossierOverviewScreenComponent,
|
|
||||||
canActivate: [CompositeRouteGuard],
|
canActivate: [CompositeRouteGuard],
|
||||||
data: {
|
data: {
|
||||||
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard],
|
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard],
|
||||||
reuse: true
|
reuse: true
|
||||||
}
|
},
|
||||||
|
loadChildren: () => import('./screens/dossier-overview/dossier-overview.module').then(m => m.DossierOverviewModule)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: ':dossierId/file/:fileId',
|
path: ':dossierId/file/:fileId',
|
||||||
@ -39,12 +37,12 @@ const routes: Routes = [
|
|||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
pathMatch: 'full',
|
pathMatch: 'full',
|
||||||
component: DossierListingScreenComponent,
|
|
||||||
canActivate: [CompositeRouteGuard],
|
canActivate: [CompositeRouteGuard],
|
||||||
data: {
|
data: {
|
||||||
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard],
|
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard],
|
||||||
reuse: true
|
reuse: true
|
||||||
}
|
},
|
||||||
|
loadChildren: () => import('./screens/dossiers-listing/dossiers-listing.module').then(m => m.DossiersListingModule)
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,5 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { DossierListingScreenComponent } from './screens/dossier-listing-screen/dossier-listing-screen.component';
|
|
||||||
import { DossierOverviewScreenComponent } from './screens/dossier-overview-screen/dossier-overview-screen.component';
|
|
||||||
import { FilePreviewScreenComponent } from './screens/file-preview-screen/file-preview-screen.component';
|
import { FilePreviewScreenComponent } from './screens/file-preview-screen/file-preview-screen.component';
|
||||||
import { AddDossierDialogComponent } from './dialogs/add-dossier-dialog/add-dossier-dialog.component';
|
import { AddDossierDialogComponent } from './dialogs/add-dossier-dialog/add-dossier-dialog.component';
|
||||||
import { AssignReviewerApproverDialogComponent } from './dialogs/assign-reviewer-approver-dialog/assign-reviewer-approver-dialog.component';
|
import { AssignReviewerApproverDialogComponent } from './dialogs/assign-reviewer-approver-dialog/assign-reviewer-approver-dialog.component';
|
||||||
@ -11,17 +9,9 @@ import { RemoveAnnotationsDialogComponent } from './dialogs/remove-annotations-d
|
|||||||
import { DocumentInfoDialogComponent } from './dialogs/document-info-dialog/document-info-dialog.component';
|
import { DocumentInfoDialogComponent } from './dialogs/document-info-dialog/document-info-dialog.component';
|
||||||
import { PdfViewerComponent } from './components/pdf-viewer/pdf-viewer.component';
|
import { PdfViewerComponent } from './components/pdf-viewer/pdf-viewer.component';
|
||||||
import { CommentsComponent } from './components/comments/comments.component';
|
import { CommentsComponent } from './components/comments/comments.component';
|
||||||
import { DossierDetailsComponent } from './components/dossier-details/dossier-details.component';
|
|
||||||
import { PageIndicatorComponent } from './components/page-indicator/page-indicator.component';
|
import { PageIndicatorComponent } from './components/page-indicator/page-indicator.component';
|
||||||
import { NeedsWorkBadgeComponent } from './components/needs-work-badge/needs-work-badge.component';
|
|
||||||
import { AnnotationActionsComponent } from './components/annotation-actions/annotation-actions.component';
|
import { AnnotationActionsComponent } from './components/annotation-actions/annotation-actions.component';
|
||||||
import { DossierListingDetailsComponent } from './components/dossier-listing-details/dossier-listing-details.component';
|
|
||||||
import { FileActionsComponent } from './components/file-actions/file-actions.component';
|
|
||||||
import { TypeAnnotationIconComponent } from './components/type-annotation-icon/type-annotation-icon.component';
|
import { TypeAnnotationIconComponent } from './components/type-annotation-icon/type-annotation-icon.component';
|
||||||
import { TypeFilterComponent } from './components/type-filter/type-filter.component';
|
|
||||||
import { DossierOverviewBulkActionsComponent } from './components/bulk-actions/dossier-overview-bulk-actions.component';
|
|
||||||
import { TeamMembersComponent } from './components/team-members/team-members.component';
|
|
||||||
import { DossierListingActionsComponent } from './components/dossier-listing-actions/dossier-listing-actions.component';
|
|
||||||
import { DocumentInfoComponent } from './components/document-info/document-info.component';
|
import { DocumentInfoComponent } from './components/document-info/document-info.component';
|
||||||
import { FileWorkloadComponent } from './components/file-workload/file-workload.component';
|
import { FileWorkloadComponent } from './components/file-workload/file-workload.component';
|
||||||
import { SharedModule } from '@shared/shared.module';
|
import { SharedModule } from '@shared/shared.module';
|
||||||
@ -29,7 +19,6 @@ import { DossiersRoutingModule } from './dossiers-routing.module';
|
|||||||
import { FileUploadDownloadModule } from '@upload-download/file-upload-download.module';
|
import { FileUploadDownloadModule } from '@upload-download/file-upload-download.module';
|
||||||
import { DossiersDialogService } from './services/dossiers-dialog.service';
|
import { DossiersDialogService } from './services/dossiers-dialog.service';
|
||||||
import { AnnotationActionsService } from './services/annotation-actions.service';
|
import { AnnotationActionsService } from './services/annotation-actions.service';
|
||||||
import { FileActionService } from './services/file-action.service';
|
|
||||||
import { PdfViewerDataService } from './services/pdf-viewer-data.service';
|
import { PdfViewerDataService } from './services/pdf-viewer-data.service';
|
||||||
import { ManualAnnotationService } from './services/manual-annotation.service';
|
import { ManualAnnotationService } from './services/manual-annotation.service';
|
||||||
import { AnnotationDrawService } from './services/annotation-draw.service';
|
import { AnnotationDrawService } from './services/annotation-draw.service';
|
||||||
@ -46,14 +35,14 @@ import { PageExclusionComponent } from './components/page-exclusion/page-exclusi
|
|||||||
import { RecategorizeImageDialogComponent } from './dialogs/recategorize-image-dialog/recategorize-image-dialog.component';
|
import { RecategorizeImageDialogComponent } from './dialogs/recategorize-image-dialog/recategorize-image-dialog.component';
|
||||||
import { EditDossierAttributesComponent } from './dialogs/edit-dossier-dialog/attributes/edit-dossier-attributes.component';
|
import { EditDossierAttributesComponent } from './dialogs/edit-dossier-dialog/attributes/edit-dossier-attributes.component';
|
||||||
import { DossiersService } from './services/dossiers.service';
|
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 { SearchScreenComponent } from './screens/search-screen/search-screen.component';
|
||||||
import { EditDossierDeletedDocumentsComponent } from './dialogs/edit-dossier-dialog/deleted-documents/edit-dossier-deleted-documents.component';
|
import { EditDossierDeletedDocumentsComponent } from './dialogs/edit-dossier-dialog/deleted-documents/edit-dossier-deleted-documents.component';
|
||||||
import { AnnotationsListComponent } from './components/file-workload/components/annotations-list/annotations-list.component';
|
import { AnnotationsListComponent } from './components/file-workload/components/annotations-list/annotations-list.component';
|
||||||
import { AnnotationSourceComponent } from './components/file-workload/components/annotation-source/annotation-source.component';
|
import { AnnotationSourceComponent } from './components/file-workload/components/annotation-source/annotation-source.component';
|
||||||
import { OverlayModule } from '@angular/cdk/overlay';
|
import { OverlayModule } from '@angular/cdk/overlay';
|
||||||
|
import { SharedDossiersModule } from './shared/shared-dossiers.module';
|
||||||
|
|
||||||
const screens = [DossierListingScreenComponent, DossierOverviewScreenComponent, FilePreviewScreenComponent, SearchScreenComponent];
|
const screens = [FilePreviewScreenComponent, SearchScreenComponent];
|
||||||
|
|
||||||
const dialogs = [
|
const dialogs = [
|
||||||
AddDossierDialogComponent,
|
AddDossierDialogComponent,
|
||||||
@ -70,17 +59,9 @@ const dialogs = [
|
|||||||
const components = [
|
const components = [
|
||||||
PdfViewerComponent,
|
PdfViewerComponent,
|
||||||
CommentsComponent,
|
CommentsComponent,
|
||||||
DossierDetailsComponent,
|
|
||||||
PageIndicatorComponent,
|
PageIndicatorComponent,
|
||||||
NeedsWorkBadgeComponent,
|
|
||||||
AnnotationActionsComponent,
|
AnnotationActionsComponent,
|
||||||
DossierListingDetailsComponent,
|
|
||||||
TypeAnnotationIconComponent,
|
TypeAnnotationIconComponent,
|
||||||
TypeFilterComponent,
|
|
||||||
DossierOverviewBulkActionsComponent,
|
|
||||||
FileActionsComponent,
|
|
||||||
TeamMembersComponent,
|
|
||||||
DossierListingActionsComponent,
|
|
||||||
DocumentInfoComponent,
|
DocumentInfoComponent,
|
||||||
FileWorkloadComponent,
|
FileWorkloadComponent,
|
||||||
EditDossierGeneralInfoComponent,
|
EditDossierGeneralInfoComponent,
|
||||||
@ -90,7 +71,6 @@ const components = [
|
|||||||
EditDossierAttributesComponent,
|
EditDossierAttributesComponent,
|
||||||
TeamMembersManagerComponent,
|
TeamMembersManagerComponent,
|
||||||
PageExclusionComponent,
|
PageExclusionComponent,
|
||||||
DossierDetailsStatsComponent,
|
|
||||||
EditDossierDeletedDocumentsComponent,
|
EditDossierDeletedDocumentsComponent,
|
||||||
AnnotationsListComponent,
|
AnnotationsListComponent,
|
||||||
AnnotationSourceComponent,
|
AnnotationSourceComponent,
|
||||||
@ -102,7 +82,6 @@ const components = [
|
|||||||
const services = [
|
const services = [
|
||||||
DossiersService,
|
DossiersService,
|
||||||
DossiersDialogService,
|
DossiersDialogService,
|
||||||
FileActionService,
|
|
||||||
AnnotationActionsService,
|
AnnotationActionsService,
|
||||||
ManualAnnotationService,
|
ManualAnnotationService,
|
||||||
PdfViewerDataService,
|
PdfViewerDataService,
|
||||||
@ -114,6 +93,6 @@ const services = [
|
|||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [...components],
|
declarations: [...components],
|
||||||
providers: [...services],
|
providers: [...services],
|
||||||
imports: [CommonModule, SharedModule, FileUploadDownloadModule, DossiersRoutingModule, OverlayModule]
|
imports: [CommonModule, SharedModule, SharedDossiersModule, FileUploadDownloadModule, DossiersRoutingModule, OverlayModule]
|
||||||
})
|
})
|
||||||
export class DossiersModule {}
|
export class DossiersModule {}
|
||||||
|
|||||||
@ -1,86 +0,0 @@
|
|||||||
<section>
|
|
||||||
<redaction-page-header [buttonConfigs]="buttonConfigs"></redaction-page-header>
|
|
||||||
|
|
||||||
<div class="overlay-shadow"></div>
|
|
||||||
|
|
||||||
<div class="red-content-inner">
|
|
||||||
<div class="content-container">
|
|
||||||
<iqser-table
|
|
||||||
(noDataAction)="openAddDossierDialog()"
|
|
||||||
[hasScrollButton]="true"
|
|
||||||
[itemSize]="85"
|
|
||||||
[noDataButtonLabel]="'dossier-listing.no-data.action' | translate"
|
|
||||||
[noDataText]="'dossier-listing.no-data.title' | translate"
|
|
||||||
[noMatchText]="'dossier-listing.no-match.title' | translate"
|
|
||||||
[showNoDataButton]="currentUser.isManager"
|
|
||||||
noDataIcon="red:folder"
|
|
||||||
></iqser-table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="right-container" iqserHasScrollbar>
|
|
||||||
<redaction-dossier-listing-details
|
|
||||||
*ngIf="(entitiesService.noData$ | async) === false"
|
|
||||||
[documentsChartData]="documentsChartData"
|
|
||||||
[dossiersChartData]="dossiersChartData"
|
|
||||||
></redaction-dossier-listing-details>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<ng-template #needsWorkFilterTemplate let-filter="filter">
|
|
||||||
<redaction-type-filter [filter]="filter"></redaction-type-filter>
|
|
||||||
</ng-template>
|
|
||||||
|
|
||||||
<ng-template #nameTemplate let-dossier="entity">
|
|
||||||
<div class="cell">
|
|
||||||
<div [matTooltip]="dossier.dossierName" class="table-item-title heading mb-6" matTooltipPosition="above">
|
|
||||||
{{ dossier.dossierName }}
|
|
||||||
</div>
|
|
||||||
<div class="small-label stats-subtitle mb-6">
|
|
||||||
<div>
|
|
||||||
<mat-icon svgIcon="red:template"></mat-icon>
|
|
||||||
{{ getDossierTemplateNameFor(dossier.dossierTemplateId) }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="small-label stats-subtitle">
|
|
||||||
<div>
|
|
||||||
<mat-icon svgIcon="red:document"></mat-icon>
|
|
||||||
{{ dossier.filesLength }}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<mat-icon svgIcon="red:pages"></mat-icon>
|
|
||||||
{{ dossier.totalNumberOfPages }}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<mat-icon svgIcon="red:user"></mat-icon>
|
|
||||||
{{ dossier.memberIds.length }}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<mat-icon svgIcon="red:calendar"></mat-icon>
|
|
||||||
{{ dossier.date | date: 'mediumDate' }}
|
|
||||||
</div>
|
|
||||||
<div *ngIf="dossier.dueDate">
|
|
||||||
<mat-icon svgIcon="red:lightning"></mat-icon>
|
|
||||||
{{ dossier.dueDate | date: 'mediumDate' }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</ng-template>
|
|
||||||
|
|
||||||
<ng-template #needsWorkTemplate let-dossier="entity">
|
|
||||||
<div class="cell">
|
|
||||||
<redaction-needs-work-badge [needsWorkInput]="dossier"></redaction-needs-work-badge>
|
|
||||||
</div>
|
|
||||||
</ng-template>
|
|
||||||
|
|
||||||
<ng-template #ownerTemplate let-dossier="entity">
|
|
||||||
<div class="cell user-column">
|
|
||||||
<redaction-initials-avatar [userId]="dossier.ownerId" [withName]="true"></redaction-initials-avatar>
|
|
||||||
</div>
|
|
||||||
</ng-template>
|
|
||||||
|
|
||||||
<ng-template #statusTemplate let-dossier="entity">
|
|
||||||
<div class="cell status-container">
|
|
||||||
<redaction-dossier-listing-actions (actionPerformed)="calculateData()" [dossier]="dossier"></redaction-dossier-listing-actions>
|
|
||||||
</div>
|
|
||||||
</ng-template>
|
|
||||||
@ -1,22 +0,0 @@
|
|||||||
:host {
|
|
||||||
::ng-deep iqser-table cdk-virtual-scroll-viewport .cdk-virtual-scroll-content-wrapper .table-item > div.cell {
|
|
||||||
&.status-container {
|
|
||||||
width: 160px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.right-container {
|
|
||||||
display: flex;
|
|
||||||
width: 466px;
|
|
||||||
min-width: 466px;
|
|
||||||
padding-right: 11px;
|
|
||||||
|
|
||||||
&.has-scrollbar:hover {
|
|
||||||
padding-right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
redaction-dossier-listing-details {
|
|
||||||
min-width: 466px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,336 +0,0 @@
|
|||||||
import { AfterViewInit, Component, forwardRef, Injector, OnDestroy, OnInit, TemplateRef, ViewChild } from '@angular/core';
|
|
||||||
import { DossierStatuses } from '@redaction/red-ui-http';
|
|
||||||
import { AppStateService } from '@state/app-state.service';
|
|
||||||
import { UserService } from '@services/user.service';
|
|
||||||
import { DoughnutChartConfig } from '@shared/components/simple-doughnut-chart/simple-doughnut-chart.component';
|
|
||||||
import { groupBy } from '@utils/functions';
|
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
|
||||||
import { Dossier } from '@state/model/dossier';
|
|
||||||
import { timer } from 'rxjs';
|
|
||||||
import { tap } 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';
|
|
||||||
import { Router } from '@angular/router';
|
|
||||||
import { DossiersDialogService } from '../../services/dossiers-dialog.service';
|
|
||||||
import { OnAttach, OnDetach } from '@utils/custom-route-reuse.strategy';
|
|
||||||
import { UserPreferenceService } from '@services/user-preference.service';
|
|
||||||
import { ButtonConfig } from '@shared/components/page-header/models/button-config.model';
|
|
||||||
import { DefaultListingServices, keyChecker, ListingComponent, NestedFilter, TableColumnConfig, TableComponent } from '@iqser/common-ui';
|
|
||||||
import { workloadTranslations } from '../../translations/workload-translations';
|
|
||||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
|
||||||
import { fileStatusTranslations } from '../../translations/file-status-translations';
|
|
||||||
import { annotationFilterChecker, dossierMemberChecker, dossierStatusChecker, dossierTemplateChecker } from '@utils/filter-utils';
|
|
||||||
import { PermissionsService } from '@services/permissions.service';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
templateUrl: './dossier-listing-screen.component.html',
|
|
||||||
styleUrls: ['./dossier-listing-screen.component.scss'],
|
|
||||||
providers: [...DefaultListingServices, { provide: ListingComponent, useExisting: forwardRef(() => DossierListingScreenComponent) }]
|
|
||||||
})
|
|
||||||
export class DossierListingScreenComponent
|
|
||||||
extends ListingComponent<Dossier>
|
|
||||||
implements OnInit, AfterViewInit, OnDestroy, OnAttach, OnDetach
|
|
||||||
{
|
|
||||||
readonly currentUser = this._userService.currentUser;
|
|
||||||
readonly tableHeaderLabel = _('dossier-listing.table-header.title');
|
|
||||||
readonly buttonConfigs: readonly ButtonConfig[] = [
|
|
||||||
{
|
|
||||||
label: _('dossier-listing.add-new'),
|
|
||||||
action: (): void => this.openAddDossierDialog(),
|
|
||||||
hide: !this.currentUser.isManager,
|
|
||||||
icon: 'red:plus',
|
|
||||||
type: 'primary'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
tableColumnConfigs: TableColumnConfig<Dossier>[];
|
|
||||||
dossiersChartData: DoughnutChartConfig[] = [];
|
|
||||||
documentsChartData: DoughnutChartConfig[] = [];
|
|
||||||
@ViewChild('nameTemplate', { static: true }) nameTemplate: TemplateRef<unknown>;
|
|
||||||
@ViewChild('needsWorkTemplate', { static: true }) needsWorkTemplate: TemplateRef<unknown>;
|
|
||||||
@ViewChild('ownerTemplate', { static: true }) ownerTemplate: TemplateRef<unknown>;
|
|
||||||
@ViewChild('statusTemplate', { static: true }) statusTemplate: TemplateRef<unknown>;
|
|
||||||
private _lastScrolledIndex: number;
|
|
||||||
@ViewChild('needsWorkFilterTemplate', {
|
|
||||||
read: TemplateRef,
|
|
||||||
static: true
|
|
||||||
})
|
|
||||||
private readonly _needsWorkFilterTemplate: TemplateRef<unknown>;
|
|
||||||
@ViewChild(TableComponent) private readonly _tableComponent: TableComponent<Dossier>;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private readonly _router: Router,
|
|
||||||
protected readonly _injector: Injector,
|
|
||||||
private readonly _userService: UserService,
|
|
||||||
readonly permissionsService: PermissionsService,
|
|
||||||
private readonly _appStateService: AppStateService,
|
|
||||||
private readonly _translateService: TranslateService,
|
|
||||||
private readonly _dialogService: DossiersDialogService,
|
|
||||||
private readonly _translateChartService: TranslateChartService,
|
|
||||||
private readonly _userPreferenceService: UserPreferenceService
|
|
||||||
) {
|
|
||||||
super(_injector);
|
|
||||||
this._appStateService.reset();
|
|
||||||
this._loadEntitiesFromState();
|
|
||||||
}
|
|
||||||
|
|
||||||
private get _activeDossiersCount(): number {
|
|
||||||
return this.entitiesService.all.filter(p => p.status === DossierStatuses.ACTIVE).length;
|
|
||||||
}
|
|
||||||
|
|
||||||
private get _inactiveDossiersCount(): number {
|
|
||||||
return this.entitiesService.all.length - this._activeDossiersCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
getDossierTemplateNameFor(dossierTemplateId: string): string {
|
|
||||||
return this._appStateService.getDossierTemplateById(dossierTemplateId).name;
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
|
||||||
this._configureTableColumns();
|
|
||||||
this.calculateData();
|
|
||||||
|
|
||||||
this.addSubscription = timer(0, 10000).subscribe(async () => {
|
|
||||||
await this._appStateService.loadAllDossiers();
|
|
||||||
this._loadEntitiesFromState();
|
|
||||||
this.calculateData();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.addSubscription = this._appStateService.fileChanged$.subscribe(() => {
|
|
||||||
this.calculateData();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
ngAfterViewInit(): void {
|
|
||||||
this.addSubscription = this._tableComponent.scrollViewport.scrolledIndexChange
|
|
||||||
.pipe(tap(index => (this._lastScrolledIndex = index)))
|
|
||||||
.subscribe();
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnAttach(): void {
|
|
||||||
this._appStateService.reset();
|
|
||||||
this._loadEntitiesFromState();
|
|
||||||
this.ngOnInit();
|
|
||||||
this.ngAfterViewInit();
|
|
||||||
this._tableComponent.scrollViewport.scrollToIndex(this._lastScrolledIndex, 'smooth');
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnDetach(): void {
|
|
||||||
this.ngOnDestroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
openAddDossierDialog(): void {
|
|
||||||
this._dialogService.openDialog('addDossier', null, null, async addResponse => {
|
|
||||||
await this._router.navigate([`/main/dossiers/${addResponse.dossier.id}`]);
|
|
||||||
if (addResponse.addMembers) {
|
|
||||||
this._dialogService.openDialog('editDossier', null, {
|
|
||||||
dossier: addResponse.dossier,
|
|
||||||
section: 'members'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
calculateData(): void {
|
|
||||||
this._computeAllFilters();
|
|
||||||
|
|
||||||
this.dossiersChartData = [
|
|
||||||
{ value: this._activeDossiersCount, color: 'ACTIVE', label: _('active') },
|
|
||||||
{ value: this._inactiveDossiersCount, color: 'DELETED', label: _('archived') }
|
|
||||||
];
|
|
||||||
const groups = groupBy(this._appStateService.aggregatedFiles, 'status');
|
|
||||||
this.documentsChartData = [];
|
|
||||||
|
|
||||||
for (const status of Object.keys(groups)) {
|
|
||||||
this.documentsChartData.push({
|
|
||||||
value: groups[status].length,
|
|
||||||
color: status,
|
|
||||||
label: fileStatusTranslations[status],
|
|
||||||
key: status
|
|
||||||
});
|
|
||||||
}
|
|
||||||
this.documentsChartData.sort(StatusSorter.byStatus);
|
|
||||||
this.documentsChartData = this._translateChartService.translateStatus(this.documentsChartData);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _configureTableColumns() {
|
|
||||||
this.tableColumnConfigs = [
|
|
||||||
{
|
|
||||||
label: _('dossier-listing.table-col-names.name'),
|
|
||||||
sortByKey: 'searchKey',
|
|
||||||
template: this.nameTemplate,
|
|
||||||
width: '2fr'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: _('dossier-listing.table-col-names.needs-work'),
|
|
||||||
template: this.needsWorkTemplate
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: _('dossier-listing.table-col-names.owner'),
|
|
||||||
class: 'user-column',
|
|
||||||
template: this.ownerTemplate
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: _('dossier-listing.table-col-names.status'),
|
|
||||||
class: 'flex-end',
|
|
||||||
template: this.statusTemplate,
|
|
||||||
width: 'auto'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
private _loadEntitiesFromState() {
|
|
||||||
this.entitiesService.setEntities(this._appStateService.allDossiers);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _computeAllFilters() {
|
|
||||||
const allDistinctFileStatus = new Set<string>();
|
|
||||||
const allDistinctPeople = new Set<string>();
|
|
||||||
const allDistinctNeedsWork = new Set<string>();
|
|
||||||
const allDistinctDossierTemplates = new Set<string>();
|
|
||||||
|
|
||||||
this.entitiesService.all?.forEach(entry => {
|
|
||||||
// all people
|
|
||||||
entry.memberIds.forEach(f => allDistinctPeople.add(f));
|
|
||||||
// Needs work
|
|
||||||
entry.files.forEach(file => {
|
|
||||||
allDistinctFileStatus.add(file.status);
|
|
||||||
if (file.analysisRequired) {
|
|
||||||
allDistinctNeedsWork.add('analysis');
|
|
||||||
}
|
|
||||||
if (entry.hintsOnly) {
|
|
||||||
allDistinctNeedsWork.add('hint');
|
|
||||||
}
|
|
||||||
if (entry.hasRedactions) {
|
|
||||||
allDistinctNeedsWork.add('redaction');
|
|
||||||
}
|
|
||||||
if (entry.hasSuggestions) {
|
|
||||||
allDistinctNeedsWork.add('suggestion');
|
|
||||||
}
|
|
||||||
if (entry.hasNone) {
|
|
||||||
allDistinctNeedsWork.add('none');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
allDistinctDossierTemplates.add(entry.dossierTemplateId);
|
|
||||||
});
|
|
||||||
|
|
||||||
const statusFilters = [...allDistinctFileStatus].map(
|
|
||||||
status =>
|
|
||||||
new NestedFilter({
|
|
||||||
id: status,
|
|
||||||
label: this._translateService.instant(fileStatusTranslations[status])
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
this.filterService.addFilterGroup({
|
|
||||||
slug: 'statusFilters',
|
|
||||||
label: this._translateService.instant('filters.status'),
|
|
||||||
icon: 'red:status',
|
|
||||||
filters: statusFilters.sort((a, b) => StatusSorter[a.id] - StatusSorter[b.id]),
|
|
||||||
checker: dossierStatusChecker
|
|
||||||
});
|
|
||||||
|
|
||||||
const peopleFilters = [...allDistinctPeople].map(
|
|
||||||
userId =>
|
|
||||||
new NestedFilter({
|
|
||||||
id: userId,
|
|
||||||
label: this._userService.getNameForId(userId)
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
this.filterService.addFilterGroup({
|
|
||||||
slug: 'peopleFilters',
|
|
||||||
label: this._translateService.instant('filters.people'),
|
|
||||||
icon: 'red:user',
|
|
||||||
filters: peopleFilters,
|
|
||||||
checker: dossierMemberChecker
|
|
||||||
});
|
|
||||||
|
|
||||||
const needsWorkFilters = [...allDistinctNeedsWork].map(
|
|
||||||
type =>
|
|
||||||
new NestedFilter({
|
|
||||||
id: type,
|
|
||||||
label: workloadTranslations[type]
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
this.filterService.addFilterGroup({
|
|
||||||
slug: 'needsWorkFilters',
|
|
||||||
label: this._translateService.instant('filters.needs-work'),
|
|
||||||
icon: 'red:needs-work',
|
|
||||||
filterTemplate: this._needsWorkFilterTemplate,
|
|
||||||
filters: needsWorkFilters.sort((a, b) => RedactionFilterSorter[a.id] - RedactionFilterSorter[b.id]),
|
|
||||||
checker: annotationFilterChecker,
|
|
||||||
matchAll: true
|
|
||||||
});
|
|
||||||
|
|
||||||
const dossierTemplateFilters = [...allDistinctDossierTemplates].map(
|
|
||||||
id =>
|
|
||||||
new NestedFilter({
|
|
||||||
id: id,
|
|
||||||
label: this._appStateService.getDossierTemplateById(id).name
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
this.filterService.addFilterGroup({
|
|
||||||
slug: 'dossierTemplateFilters',
|
|
||||||
label: this._translateService.instant('filters.dossier-templates'),
|
|
||||||
icon: 'red:template',
|
|
||||||
hide: dossierTemplateFilters.length <= 1,
|
|
||||||
filters: dossierTemplateFilters,
|
|
||||||
checker: dossierTemplateChecker
|
|
||||||
});
|
|
||||||
|
|
||||||
const quickFilters = this._createQuickFilters();
|
|
||||||
this.filterService.addFilterGroup({
|
|
||||||
slug: 'quickFilters',
|
|
||||||
filters: quickFilters,
|
|
||||||
checker: (dw: Dossier) => quickFilters.reduce((acc, f) => acc || (f.checked && f.checker(dw)), false)
|
|
||||||
});
|
|
||||||
|
|
||||||
const dossierFilters = this.entitiesService.all.map(
|
|
||||||
dossier =>
|
|
||||||
new NestedFilter({
|
|
||||||
id: dossier.dossierName,
|
|
||||||
label: dossier.dossierName
|
|
||||||
})
|
|
||||||
);
|
|
||||||
this.filterService.addFilterGroup({
|
|
||||||
slug: 'dossierNameFilter',
|
|
||||||
label: this._translateService.instant('dossier-listing.filters.label'),
|
|
||||||
icon: 'red:folder',
|
|
||||||
filters: dossierFilters,
|
|
||||||
filterceptionPlaceholder: this._translateService.instant('dossier-listing.filters.search'),
|
|
||||||
checker: keyChecker('dossierName')
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private _createQuickFilters(): NestedFilter[] {
|
|
||||||
const myDossiersLabel = this._translateService.instant('dossier-listing.quick-filters.my-dossiers');
|
|
||||||
const filters: NestedFilter[] = [
|
|
||||||
{
|
|
||||||
id: 'my-dossiers',
|
|
||||||
label: myDossiersLabel,
|
|
||||||
checker: (dw: Dossier) => dw.ownerId === this.currentUser.id
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'to-approve',
|
|
||||||
label: this._translateService.instant('dossier-listing.quick-filters.to-approve'),
|
|
||||||
checker: (dw: Dossier) => dw.approverIds.includes(this.currentUser.id)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'to-review',
|
|
||||||
label: this._translateService.instant('dossier-listing.quick-filters.to-review'),
|
|
||||||
checker: (dw: Dossier) => dw.memberIds.includes(this.currentUser.id)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'other',
|
|
||||||
label: this._translateService.instant('dossier-listing.quick-filters.other'),
|
|
||||||
checker: (dw: Dossier) => !dw.memberIds.includes(this.currentUser.id)
|
|
||||||
}
|
|
||||||
].map(filter => new NestedFilter(filter));
|
|
||||||
|
|
||||||
return filters.filter(f => f.label === myDossiersLabel || this._userPreferenceService.areDevFeaturesEnabled);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,601 +0,0 @@
|
|||||||
import {
|
|
||||||
ChangeDetectorRef,
|
|
||||||
Component,
|
|
||||||
ElementRef,
|
|
||||||
forwardRef,
|
|
||||||
HostListener,
|
|
||||||
Injector,
|
|
||||||
OnDestroy,
|
|
||||||
OnInit,
|
|
||||||
TemplateRef,
|
|
||||||
ViewChild
|
|
||||||
} from '@angular/core';
|
|
||||||
import { FileStatus, FileStatuses, IFileAttributeConfig } from '@redaction/red-ui-http';
|
|
||||||
import { AppStateService } from '@state/app-state.service';
|
|
||||||
import { FileDropOverlayService } from '@upload-download/services/file-drop-overlay.service';
|
|
||||||
import { FileUploadModel } from '@upload-download/model/file-upload.model';
|
|
||||||
import { FileUploadService } from '@upload-download/services/file-upload.service';
|
|
||||||
import { StatusOverlayService } from '@upload-download/services/status-overlay.service';
|
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
|
||||||
import * as moment from 'moment';
|
|
||||||
import { DossierDetailsComponent } from '../../components/dossier-details/dossier-details.component';
|
|
||||||
import { File } from '@models/file/file';
|
|
||||||
import { UserService } from '@services/user.service';
|
|
||||||
import { timer } from 'rxjs';
|
|
||||||
import { tap } from 'rxjs/operators';
|
|
||||||
import { RedactionFilterSorter } from '@utils/sorters/redaction-filter-sorter';
|
|
||||||
import { StatusSorter } from '@utils/sorters/status-sorter';
|
|
||||||
import { convertFiles, Files, handleFileDrop } from '@utils/file-drop-utils';
|
|
||||||
import { DossiersDialogService } from '../../services/dossiers-dialog.service';
|
|
||||||
import { OnAttach, OnDetach } from '@utils/custom-route-reuse.strategy';
|
|
||||||
import { ConfigService } from '@services/config.service';
|
|
||||||
import { ActionConfig } from '@shared/components/page-header/models/action-config.model';
|
|
||||||
import {
|
|
||||||
CircleButtonTypes,
|
|
||||||
DefaultListingServices,
|
|
||||||
INestedFilter,
|
|
||||||
keyChecker,
|
|
||||||
ListingComponent,
|
|
||||||
ListingModes,
|
|
||||||
LoadingService,
|
|
||||||
NestedFilter,
|
|
||||||
TableColumnConfig,
|
|
||||||
TableComponent,
|
|
||||||
Toaster,
|
|
||||||
WorkflowConfig
|
|
||||||
} from '@iqser/common-ui';
|
|
||||||
import { DossierAttributesService } from '@shared/services/controller-wrappers/dossier-attributes.service';
|
|
||||||
import { DossierAttributeWithValue } from '@models/dossier-attributes.model';
|
|
||||||
import { workloadTranslations } from '../../translations/workload-translations';
|
|
||||||
import { fileStatusTranslations } from '../../translations/file-status-translations';
|
|
||||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
|
||||||
import { annotationFilterChecker } from '@utils/filter-utils';
|
|
||||||
import { PermissionsService } from '@services/permissions.service';
|
|
||||||
import { RouterHistoryService } from '@services/router-history.service';
|
|
||||||
import { Dossier } from '@state/model/dossier';
|
|
||||||
import { Router } from '@angular/router';
|
|
||||||
import { FileActionService } from '../../services/file-action.service';
|
|
||||||
import { FileAttributesService } from '../../services/file-attributes.service';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
templateUrl: './dossier-overview-screen.component.html',
|
|
||||||
styleUrls: ['./dossier-overview-screen.component.scss'],
|
|
||||||
providers: [...DefaultListingServices, { provide: ListingComponent, useExisting: forwardRef(() => DossierOverviewScreenComponent) }]
|
|
||||||
})
|
|
||||||
export class DossierOverviewScreenComponent extends ListingComponent<File> implements OnInit, OnDestroy, OnDetach, OnAttach {
|
|
||||||
readonly listingModes = ListingModes;
|
|
||||||
readonly circleButtonTypes = CircleButtonTypes;
|
|
||||||
readonly currentUser = this._userService.currentUser;
|
|
||||||
currentDossier = this._appStateService.activeDossier;
|
|
||||||
readonly tableHeaderLabel = _('dossier-overview.table-header.title');
|
|
||||||
readonly actionConfigs: readonly ActionConfig[] = [
|
|
||||||
{
|
|
||||||
label: this._translateService.instant('dossier-overview.header-actions.edit'),
|
|
||||||
action: ($event): void => this.openEditDossierDialog($event),
|
|
||||||
icon: 'iqser:edit',
|
|
||||||
hide: !this.currentUser.isManager
|
|
||||||
}
|
|
||||||
];
|
|
||||||
tableColumnConfigs: readonly TableColumnConfig<File>[] = [];
|
|
||||||
collapsedDetails = false;
|
|
||||||
dossierAttributes: DossierAttributeWithValue[] = [];
|
|
||||||
fileAttributeConfigs: IFileAttributeConfig[];
|
|
||||||
@ViewChild('filenameTemplate', { static: true }) filenameTemplate: TemplateRef<unknown>;
|
|
||||||
@ViewChild('addedOnTemplate', { static: true }) addedOnTemplate: TemplateRef<unknown>;
|
|
||||||
@ViewChild('attributeTemplate', { static: true }) attributeTemplate: TemplateRef<unknown>;
|
|
||||||
@ViewChild('needsWorkTemplate', { static: true }) needsWorkTemplate: TemplateRef<unknown>;
|
|
||||||
@ViewChild('reviewerTemplate', { static: true }) reviewerTemplate: TemplateRef<unknown>;
|
|
||||||
@ViewChild('pagesTemplate', { static: true }) pagesTemplate: TemplateRef<unknown>;
|
|
||||||
@ViewChild('statusTemplate', { static: true }) statusTemplate: TemplateRef<unknown>;
|
|
||||||
readonly workflowConfig: WorkflowConfig<File, FileStatus>;
|
|
||||||
@ViewChild(DossierDetailsComponent, { static: false })
|
|
||||||
private readonly _dossierDetailsComponent: DossierDetailsComponent;
|
|
||||||
private _lastScrolledIndex: number;
|
|
||||||
@ViewChild('needsWorkFilterTemplate', { read: TemplateRef, static: true })
|
|
||||||
private readonly _needsWorkFilterTemplate: TemplateRef<unknown>;
|
|
||||||
@ViewChild('fileInput') private readonly _fileInput: ElementRef;
|
|
||||||
@ViewChild(TableComponent) private readonly _tableComponent: TableComponent<Dossier>;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private readonly _toaster: Toaster,
|
|
||||||
protected readonly _injector: Injector,
|
|
||||||
private readonly _router: Router,
|
|
||||||
private readonly _userService: UserService,
|
|
||||||
readonly permissionsService: PermissionsService,
|
|
||||||
private readonly _loadingService: LoadingService,
|
|
||||||
private readonly _appStateService: AppStateService,
|
|
||||||
readonly routerHistoryService: RouterHistoryService,
|
|
||||||
private readonly _configService: ConfigService,
|
|
||||||
private readonly _translateService: TranslateService,
|
|
||||||
private readonly _dialogService: DossiersDialogService,
|
|
||||||
private readonly _changeDetectorRef: ChangeDetectorRef,
|
|
||||||
private readonly _fileUploadService: FileUploadService,
|
|
||||||
private readonly _statusOverlayService: StatusOverlayService,
|
|
||||||
private readonly _fileDropOverlayService: FileDropOverlayService,
|
|
||||||
private readonly _dossierAttributesService: DossierAttributesService,
|
|
||||||
private readonly _fileActionService: FileActionService,
|
|
||||||
private readonly _fileAttributesService: FileAttributesService
|
|
||||||
) {
|
|
||||||
super(_injector);
|
|
||||||
this._loadEntitiesFromState();
|
|
||||||
this.fileAttributeConfigs = this._fileAttributesService.getFileAttributeConfig(
|
|
||||||
this.currentDossier.dossierTemplateId
|
|
||||||
)?.fileAttributeConfigs;
|
|
||||||
this.workflowConfig = {
|
|
||||||
columnIdentifierFn: entity => entity.status,
|
|
||||||
itemVersionFn: (entity: File) => `${entity.lastUpdated}-${entity.numberOfAnalyses}`,
|
|
||||||
columns: [
|
|
||||||
{
|
|
||||||
label: fileStatusTranslations[FileStatuses.UNASSIGNED],
|
|
||||||
key: FileStatuses.UNASSIGNED,
|
|
||||||
enterFn: this.unassignFn,
|
|
||||||
enterPredicate: () => false,
|
|
||||||
color: '#D3D5DA'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: fileStatusTranslations[FileStatuses.UNDER_REVIEW],
|
|
||||||
enterFn: this.underReviewFn,
|
|
||||||
enterPredicate: (file: File) =>
|
|
||||||
this.permissionsService.canSetUnderReview(file) ||
|
|
||||||
this.permissionsService.canAssignToSelf(file) ||
|
|
||||||
this.permissionsService.canAssignUser(file),
|
|
||||||
key: FileStatuses.UNDER_REVIEW,
|
|
||||||
color: '#FDBD00'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: fileStatusTranslations[FileStatuses.UNDER_APPROVAL],
|
|
||||||
enterFn: this.underApprovalFn,
|
|
||||||
enterPredicate: (file: File) =>
|
|
||||||
this.permissionsService.canSetUnderApproval(file) || this.permissionsService.canUndoApproval(file),
|
|
||||||
key: FileStatuses.UNDER_APPROVAL,
|
|
||||||
color: '#374C81'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: fileStatusTranslations[FileStatuses.APPROVED],
|
|
||||||
enterFn: this.approveFn,
|
|
||||||
enterPredicate: (file: File) => this.permissionsService.isReadyForApproval(file),
|
|
||||||
key: FileStatuses.APPROVED,
|
|
||||||
color: '#48C9F7'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
get checkedRequiredFilters() {
|
|
||||||
return this.filterService.getGroup('quickFilters')?.filters.filter(f => f.required && f.checked);
|
|
||||||
}
|
|
||||||
|
|
||||||
get checkedNotRequiredFilters() {
|
|
||||||
return this.filterService.getGroup('quickFilters')?.filters.filter(f => !f.required && f.checked);
|
|
||||||
}
|
|
||||||
|
|
||||||
get displayedInFileListAttributes() {
|
|
||||||
return this.fileAttributeConfigs?.filter(config => config.displayedInFileList) || [];
|
|
||||||
}
|
|
||||||
|
|
||||||
unassignFn = async (file: File) => {
|
|
||||||
// TODO
|
|
||||||
console.log('unassign', file);
|
|
||||||
};
|
|
||||||
|
|
||||||
underReviewFn = (file: File) => {
|
|
||||||
this._fileActionService.assignFile('reviewer', null, file, () => this._loadingService.loadWhile(this.reloadDossiers()), true);
|
|
||||||
};
|
|
||||||
|
|
||||||
underApprovalFn = async (file: File) => {
|
|
||||||
if (this._appStateService.activeDossier.approverIds.length > 1) {
|
|
||||||
this._fileActionService.assignFile('approver', null, file, () => this._loadingService.loadWhile(this.reloadDossiers()), true);
|
|
||||||
} else {
|
|
||||||
this._loadingService.start();
|
|
||||||
await this._fileActionService.setFilesUnderApproval([file]).toPromise();
|
|
||||||
await this.reloadDossiers();
|
|
||||||
this._loadingService.stop();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
approveFn = async (file: File) => {
|
|
||||||
this._loadingService.start();
|
|
||||||
await this._fileActionService.setFilesApproved([file]).toPromise();
|
|
||||||
await this.reloadDossiers();
|
|
||||||
this._loadingService.stop();
|
|
||||||
};
|
|
||||||
|
|
||||||
actionPerformed(action?: string, file?: File) {
|
|
||||||
this.calculateData();
|
|
||||||
|
|
||||||
if (action === 'navigate') {
|
|
||||||
this._router.navigate([file.routerLink]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
disabledFn = (fileStatus: File) => fileStatus.excluded;
|
|
||||||
|
|
||||||
lastOpenedFn = (fileStatus: File) => fileStatus.lastOpened;
|
|
||||||
|
|
||||||
async ngOnInit(): Promise<void> {
|
|
||||||
this._configureTableColumns();
|
|
||||||
this._loadingService.start();
|
|
||||||
try {
|
|
||||||
this._fileDropOverlayService.initFileDropHandling();
|
|
||||||
|
|
||||||
this.calculateData();
|
|
||||||
|
|
||||||
this.addSubscription = timer(0, 7500).subscribe(async () => {
|
|
||||||
await this._appStateService.reloadActiveDossierFilesIfNecessary();
|
|
||||||
this._loadEntitiesFromState();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.addSubscription = this._appStateService.fileChanged$.subscribe(() => {
|
|
||||||
this.calculateData();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.addSubscription = this._appStateService.dossierTemplateChanged$.subscribe(() => {
|
|
||||||
this.fileAttributeConfigs = this._fileAttributesService.getFileAttributeConfig(
|
|
||||||
this.currentDossier.dossierTemplateId
|
|
||||||
)?.fileAttributeConfigs;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.addSubscription = this._tableComponent.scrollViewport.scrolledIndexChange
|
|
||||||
.pipe(tap(index => (this._lastScrolledIndex = index)))
|
|
||||||
.subscribe();
|
|
||||||
|
|
||||||
this.dossierAttributes = await this._dossierAttributesService.getValues(this.currentDossier);
|
|
||||||
} catch (e) {
|
|
||||||
console.log('Error from dossier overview screen: ', e);
|
|
||||||
} finally {
|
|
||||||
this._loadingService.stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
|
||||||
this._fileDropOverlayService.cleanupFileDropHandling();
|
|
||||||
super.ngOnDestroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
async ngOnAttach() {
|
|
||||||
await this._appStateService.reloadActiveDossierFiles();
|
|
||||||
this._loadEntitiesFromState();
|
|
||||||
await this.ngOnInit();
|
|
||||||
this._tableComponent.scrollViewport.scrollToIndex(this._lastScrolledIndex, 'smooth');
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnDetach() {
|
|
||||||
this.ngOnDestroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
async reanalyseDossier() {
|
|
||||||
try {
|
|
||||||
await this._appStateService.reanalyzeDossier();
|
|
||||||
await this.reloadDossiers();
|
|
||||||
this._toaster.success(_('dossier-overview.reanalyse-dossier.success'));
|
|
||||||
} catch (e) {
|
|
||||||
this._toaster.error(_('dossier-overview.reanalyse-dossier.error'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async reloadDossiers() {
|
|
||||||
await this._appStateService.getFiles(this.currentDossier, false);
|
|
||||||
this.calculateData();
|
|
||||||
}
|
|
||||||
|
|
||||||
calculateData(): void {
|
|
||||||
if (!this._appStateService.activeDossierId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._loadEntitiesFromState();
|
|
||||||
this._computeAllFilters();
|
|
||||||
|
|
||||||
this._dossierDetailsComponent?.calculateChartConfig();
|
|
||||||
this._changeDetectorRef.detectChanges();
|
|
||||||
}
|
|
||||||
|
|
||||||
@HostListener('drop', ['$event'])
|
|
||||||
onDrop(event: DragEvent): void {
|
|
||||||
handleFileDrop(event, this.currentDossier, this._uploadFiles.bind(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
@HostListener('dragover', ['$event'])
|
|
||||||
onDragOver(event): void {
|
|
||||||
event.stopPropagation();
|
|
||||||
event.preventDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
async uploadFiles(files: Files): Promise<void> {
|
|
||||||
await this._uploadFiles(convertFiles(files, this.currentDossier));
|
|
||||||
this._fileInput.nativeElement.value = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
async bulkActionPerformed(): Promise<void> {
|
|
||||||
this.entitiesService.setSelected([]);
|
|
||||||
await this.reloadDossiers();
|
|
||||||
}
|
|
||||||
|
|
||||||
openEditDossierDialog($event: MouseEvent) {
|
|
||||||
this._dialogService.openDialog('editDossier', $event, {
|
|
||||||
dossier: this.currentDossier
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
openAssignDossierMembersDialog(): void {
|
|
||||||
const data = { dossier: this.currentDossier, section: 'members' };
|
|
||||||
this._dialogService.openDialog('editDossier', null, data, async () => await this.reloadDossiers());
|
|
||||||
}
|
|
||||||
|
|
||||||
openDossierDictionaryDialog() {
|
|
||||||
const data = { dossier: this.currentDossier, section: 'dossierDictionary' };
|
|
||||||
this._dialogService.openDialog('editDossier', null, data, async () => {
|
|
||||||
await this.reloadDossiers();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleCollapsedDetails() {
|
|
||||||
this.collapsedDetails = !this.collapsedDetails;
|
|
||||||
}
|
|
||||||
|
|
||||||
recentlyModifiedChecker = (file: File) =>
|
|
||||||
moment(file.lastUpdated).add(this._configService.values.RECENT_PERIOD_IN_HOURS, 'hours').isAfter(moment());
|
|
||||||
|
|
||||||
private _configureTableColumns() {
|
|
||||||
const dynamicColumns: TableColumnConfig<File>[] = [];
|
|
||||||
for (const config of this.displayedInFileListAttributes) {
|
|
||||||
if (config.displayedInFileList) {
|
|
||||||
dynamicColumns.push({ label: config.label, notTranslatable: true, template: this.attributeTemplate, extra: config });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.tableColumnConfigs = [
|
|
||||||
{
|
|
||||||
label: _('dossier-overview.table-col-names.name'),
|
|
||||||
sortByKey: 'searchKey',
|
|
||||||
template: this.filenameTemplate,
|
|
||||||
width: '3fr'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: _('dossier-overview.table-col-names.added-on'),
|
|
||||||
sortByKey: 'added',
|
|
||||||
template: this.addedOnTemplate,
|
|
||||||
width: '2fr'
|
|
||||||
},
|
|
||||||
...dynamicColumns,
|
|
||||||
{
|
|
||||||
label: _('dossier-overview.table-col-names.needs-work'),
|
|
||||||
template: this.needsWorkTemplate
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: _('dossier-overview.table-col-names.assigned-to'),
|
|
||||||
class: 'user-column',
|
|
||||||
sortByKey: 'reviewerName',
|
|
||||||
template: this.reviewerTemplate,
|
|
||||||
width: '2fr'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: _('dossier-overview.table-col-names.pages'),
|
|
||||||
sortByKey: 'numberOfPages',
|
|
||||||
template: this.pagesTemplate
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: _('dossier-overview.table-col-names.status'),
|
|
||||||
class: 'flex-end',
|
|
||||||
sortByKey: 'statusSort',
|
|
||||||
template: this.statusTemplate
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
private _loadEntitiesFromState() {
|
|
||||||
this.currentDossier = this._appStateService.activeDossier;
|
|
||||||
if (this.currentDossier) {
|
|
||||||
this.entitiesService.setEntities(this.currentDossier.files);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _uploadFiles(files: FileUploadModel[]) {
|
|
||||||
const fileCount = await this._fileUploadService.uploadFiles(files);
|
|
||||||
if (fileCount) {
|
|
||||||
this._statusOverlayService.openUploadStatusOverlay();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private _computeAllFilters() {
|
|
||||||
if (!this.currentDossier) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const allDistinctFileStatuses = new Set<string>();
|
|
||||||
const allDistinctPeople = new Set<string>();
|
|
||||||
const allDistinctAddedDates = new Set<string>();
|
|
||||||
const allDistinctNeedsWork = new Set<string>();
|
|
||||||
|
|
||||||
const dynamicFilters = new Map<string, Set<string>>();
|
|
||||||
|
|
||||||
this.entitiesService.all.forEach(file => {
|
|
||||||
allDistinctPeople.add(file.currentReviewer);
|
|
||||||
allDistinctFileStatuses.add(file.status);
|
|
||||||
allDistinctAddedDates.add(moment(file.added).format('DD/MM/YYYY'));
|
|
||||||
|
|
||||||
if (file.analysisRequired) {
|
|
||||||
allDistinctNeedsWork.add('analysis');
|
|
||||||
}
|
|
||||||
if (file.hintsOnly) {
|
|
||||||
allDistinctNeedsWork.add('hint');
|
|
||||||
}
|
|
||||||
if (file.hasRedactions) {
|
|
||||||
allDistinctNeedsWork.add('redaction');
|
|
||||||
}
|
|
||||||
if (file.hasSuggestions) {
|
|
||||||
allDistinctNeedsWork.add('suggestion');
|
|
||||||
}
|
|
||||||
if (file.hasUpdates) {
|
|
||||||
allDistinctNeedsWork.add('updated');
|
|
||||||
}
|
|
||||||
if (file.hasImages) {
|
|
||||||
allDistinctNeedsWork.add('image');
|
|
||||||
}
|
|
||||||
if (file.hasNone) {
|
|
||||||
allDistinctNeedsWork.add('none');
|
|
||||||
}
|
|
||||||
if (file.hasAnnotationComments) {
|
|
||||||
allDistinctNeedsWork.add('comment');
|
|
||||||
}
|
|
||||||
|
|
||||||
// extract values for dynamic filters
|
|
||||||
this.fileAttributeConfigs.forEach(config => {
|
|
||||||
if (config.filterable) {
|
|
||||||
const filterKey = `${config.id}:${config.label}`;
|
|
||||||
let filters = dynamicFilters.get(filterKey);
|
|
||||||
if (!filters) {
|
|
||||||
dynamicFilters.set(filterKey, new Set<string>());
|
|
||||||
filters = dynamicFilters.get(filterKey);
|
|
||||||
}
|
|
||||||
let filterValue = file.fileAttributes?.attributeIdToValue[config.id];
|
|
||||||
if (!filterValue) {
|
|
||||||
filterValue = '-';
|
|
||||||
file.fileAttributes.attributeIdToValue[config.id] = '-';
|
|
||||||
}
|
|
||||||
filters.add(filterValue);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const statusFilters = [...allDistinctFileStatuses].map(
|
|
||||||
status =>
|
|
||||||
new NestedFilter({
|
|
||||||
id: status,
|
|
||||||
label: this._translateService.instant(fileStatusTranslations[status])
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
this.filterService.addFilterGroup({
|
|
||||||
slug: 'statusFilters',
|
|
||||||
label: this._translateService.instant('filters.status'),
|
|
||||||
icon: 'red:status',
|
|
||||||
filters: statusFilters.sort((a, b) => StatusSorter[a.id] - StatusSorter[b.id]),
|
|
||||||
checker: keyChecker('status')
|
|
||||||
});
|
|
||||||
|
|
||||||
const peopleFilters: NestedFilter[] = [];
|
|
||||||
if (allDistinctPeople.has(undefined) || allDistinctPeople.has(null)) {
|
|
||||||
allDistinctPeople.delete(undefined);
|
|
||||||
allDistinctPeople.delete(null);
|
|
||||||
peopleFilters.push(
|
|
||||||
new NestedFilter({
|
|
||||||
id: null,
|
|
||||||
label: this._translateService.instant('initials-avatar.unassigned')
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
allDistinctPeople.forEach(userId => {
|
|
||||||
peopleFilters.push(
|
|
||||||
new NestedFilter({
|
|
||||||
id: userId,
|
|
||||||
label: this._userService.getNameForId(userId)
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
|
||||||
this.filterService.addFilterGroup({
|
|
||||||
slug: 'peopleFilters',
|
|
||||||
label: this._translateService.instant('filters.assigned-people'),
|
|
||||||
icon: 'red:user',
|
|
||||||
filters: peopleFilters,
|
|
||||||
checker: keyChecker('currentReviewer')
|
|
||||||
});
|
|
||||||
|
|
||||||
const needsWorkFilters = [...allDistinctNeedsWork].map(
|
|
||||||
item =>
|
|
||||||
new NestedFilter({
|
|
||||||
id: item,
|
|
||||||
label: workloadTranslations[item]
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
this.filterService.addFilterGroup({
|
|
||||||
slug: 'needsWorkFilters',
|
|
||||||
label: this._translateService.instant('filters.needs-work'),
|
|
||||||
icon: 'red:needs-work',
|
|
||||||
filterTemplate: this._needsWorkFilterTemplate,
|
|
||||||
filters: needsWorkFilters.sort(RedactionFilterSorter.byKey),
|
|
||||||
checker: annotationFilterChecker,
|
|
||||||
matchAll: true
|
|
||||||
});
|
|
||||||
|
|
||||||
dynamicFilters.forEach((filterValue: Set<string>, filterKey: string) => {
|
|
||||||
const id = filterKey.split(':')[0];
|
|
||||||
const key = filterKey.split(':')[1];
|
|
||||||
this.filterService.addFilterGroup({
|
|
||||||
slug: key,
|
|
||||||
label: key,
|
|
||||||
icon: 'red:template',
|
|
||||||
filters: [...filterValue].map(
|
|
||||||
(value: string) =>
|
|
||||||
new NestedFilter({
|
|
||||||
id: value,
|
|
||||||
label: value === '-' ? this._translateService.instant('filters.empty') : value
|
|
||||||
})
|
|
||||||
),
|
|
||||||
checker: (input: File, filter: INestedFilter) => filter.id === input.fileAttributes.attributeIdToValue[id]
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
this.filterService.addFilterGroup({
|
|
||||||
slug: 'quickFilters',
|
|
||||||
filters: this._createQuickFilters(),
|
|
||||||
checker: (file: File) =>
|
|
||||||
this.checkedRequiredFilters.reduce((acc, f) => acc && f.checker(file), true) &&
|
|
||||||
(this.checkedNotRequiredFilters.length === 0 ||
|
|
||||||
this.checkedNotRequiredFilters.reduce((acc, f) => acc || f.checker(file), false))
|
|
||||||
});
|
|
||||||
|
|
||||||
const filesNamesFilters = this.entitiesService.all.map(
|
|
||||||
file =>
|
|
||||||
new NestedFilter({
|
|
||||||
id: file.filename,
|
|
||||||
label: file.filename
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
this.filterService.addFilterGroup({
|
|
||||||
slug: 'filesNamesFilter',
|
|
||||||
label: this._translateService.instant('dossier-overview.filters.label'),
|
|
||||||
icon: 'red:document',
|
|
||||||
filters: filesNamesFilters,
|
|
||||||
checker: keyChecker('filename'),
|
|
||||||
filterceptionPlaceholder: this._translateService.instant('dossier-overview.filters.search')
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private _createQuickFilters(): NestedFilter[] {
|
|
||||||
let quickFilters: INestedFilter[] = [];
|
|
||||||
if (this.entitiesService.all.filter(this.recentlyModifiedChecker).length > 0) {
|
|
||||||
const recentPeriod = this._configService.values.RECENT_PERIOD_IN_HOURS;
|
|
||||||
quickFilters = [
|
|
||||||
{
|
|
||||||
id: 'recent',
|
|
||||||
label: this._translateService.instant('dossier-overview.quick-filters.recent', {
|
|
||||||
hours: recentPeriod
|
|
||||||
}),
|
|
||||||
required: true,
|
|
||||||
checker: this.recentlyModifiedChecker
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
...quickFilters,
|
|
||||||
{
|
|
||||||
id: 'assigned-to-me',
|
|
||||||
label: this._translateService.instant('dossier-overview.quick-filters.assigned-to-me'),
|
|
||||||
checker: (file: File) => file.currentReviewer === this.currentUser.id
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'unassigned',
|
|
||||||
label: this._translateService.instant('dossier-overview.quick-filters.unassigned'),
|
|
||||||
checker: (file: File) => !file.currentReviewer
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'assigned-to-others',
|
|
||||||
label: this._translateService.instant('dossier-overview.quick-filters.assigned-to-others'),
|
|
||||||
checker: (file: File) => !!file.currentReviewer && file.currentReviewer !== this.currentUser.id
|
|
||||||
}
|
|
||||||
].map(filter => new NestedFilter(filter));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,13 +1,13 @@
|
|||||||
import { Component, EventEmitter, Output } from '@angular/core';
|
import { Component, EventEmitter, Output } from '@angular/core';
|
||||||
import { AppStateService } from '@state/app-state.service';
|
import { AppStateService } from '../../../../../../state/app-state.service';
|
||||||
import { FileManagementControllerService, ReanalysisControllerService } from '@redaction/red-ui-http';
|
import { FileManagementControllerService, ReanalysisControllerService } from '@redaction/red-ui-http';
|
||||||
import { PermissionsService } from '@services/permissions.service';
|
import { PermissionsService } from '../../../../../../services/permissions.service';
|
||||||
import { File } from '@models/file/file';
|
import { File } from '../../../../../../models/file/file';
|
||||||
import { FileActionService } from '../../services/file-action.service';
|
import { FileActionService } from '../../../../shared/services/file-action.service';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { DossiersDialogService } from '../../services/dossiers-dialog.service';
|
import { DossiersDialogService } from '../../../../services/dossiers-dialog.service';
|
||||||
import { CircleButtonTypes, EntitiesService, LoadingService } from '@iqser/common-ui';
|
import { CircleButtonTypes, EntitiesService, LoadingService } from '@iqser/common-ui';
|
||||||
import { ConfirmationDialogInput } from '@shared/dialogs/confirmation-dialog/confirmation-dialog.component';
|
import { ConfirmationDialogInput } from '../../../../../shared/dialogs/confirmation-dialog/confirmation-dialog.component';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||||
|
|
||||||
@ -3,7 +3,7 @@ import { DossierAttributeWithValue } from '@models/dossier-attributes.model';
|
|||||||
import { AppStateService } from '@state/app-state.service';
|
import { AppStateService } from '@state/app-state.service';
|
||||||
import { Dossier } from '@state/model/dossier';
|
import { Dossier } from '@state/model/dossier';
|
||||||
import { IDossierTemplate } from '@redaction/red-ui-http';
|
import { IDossierTemplate } from '@redaction/red-ui-http';
|
||||||
import { DossiersDialogService } from '../../services/dossiers-dialog.service';
|
import { DossiersDialogService } from '../../../../services/dossiers-dialog.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'redaction-dossier-details-stats',
|
selector: 'redaction-dossier-details-stats',
|
||||||
@ -1,13 +1,12 @@
|
|||||||
import { ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
import { ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
||||||
import { AppStateService } from '@state/app-state.service';
|
import { AppStateService } from '@state/app-state.service';
|
||||||
import { groupBy } from '@utils/functions';
|
import { groupBy, StatusSorter } from '@utils/index';
|
||||||
import { DoughnutChartConfig } from '@shared/components/simple-doughnut-chart/simple-doughnut-chart.component';
|
import { DoughnutChartConfig } from '@shared/components/simple-doughnut-chart/simple-doughnut-chart.component';
|
||||||
import { TranslateChartService } from '@services/translate-chart.service';
|
import { TranslateChartService } from '@services/translate-chart.service';
|
||||||
import { StatusSorter } from '@utils/sorters/status-sorter';
|
|
||||||
import { UserService } from '@services/user.service';
|
import { UserService } from '@services/user.service';
|
||||||
import { FilterService, Toaster } from '@iqser/common-ui';
|
import { FilterService, Toaster } from '@iqser/common-ui';
|
||||||
import { DossierAttributeWithValue } from '@models/dossier-attributes.model';
|
import { DossierAttributeWithValue } from '@models/dossier-attributes.model';
|
||||||
import { fileStatusTranslations } from '../../translations/file-status-translations';
|
import { fileStatusTranslations } from '../../../../translations/file-status-translations';
|
||||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||||
import { List } from '@redaction/red-ui-http';
|
import { List } from '@redaction/red-ui-http';
|
||||||
import { User } from '@models/user';
|
import { User } from '@models/user';
|
||||||
@ -0,0 +1,70 @@
|
|||||||
|
<div class="cell">
|
||||||
|
<div>
|
||||||
|
<div [class.error]="file.isError" [matTooltip]="file.filename" class="table-item-title" matTooltipPosition="above">
|
||||||
|
{{ file.filename }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div *ngIf="file.primaryAttribute" class="small-label">
|
||||||
|
<div class="primary-attribute">
|
||||||
|
<span [matTooltip]="file.primaryAttribute" matTooltipPosition="above">
|
||||||
|
{{ file.primaryAttribute }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ng-container *ngTemplateOutlet="statsTemplate; context: { entity: file }"></ng-container>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="cell">
|
||||||
|
<div [class.error]="file.isError" class="small-label">
|
||||||
|
{{ file.added | date: 'd MMM. yyyy, hh:mm a' }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div *ngFor="let config of displayedAttributes" class="cell">
|
||||||
|
{{ file.fileAttributes.attributeIdToValue[config.id] }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- always show A for error-->
|
||||||
|
<div *ngIf="file.isError" class="cell">
|
||||||
|
<redaction-annotation-icon color="#dd4d50" label="A" type="square"></redaction-annotation-icon>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div *ngIf="!file.isError" class="cell">
|
||||||
|
<redaction-needs-work-badge [needsWorkInput]="file"></redaction-needs-work-badge>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div *ngIf="!file.isError" class="user-column cell">
|
||||||
|
<redaction-initials-avatar [userId]="file.currentReviewer" [withName]="true"></redaction-initials-avatar>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div *ngIf="!file.isError" class="cell">
|
||||||
|
<div class="small-label stats-subtitle">
|
||||||
|
<div>
|
||||||
|
<mat-icon svgIcon="red:pages"></mat-icon>
|
||||||
|
{{ file.numberOfPages }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div [class.extend-cols]="file.isError" class="status-container cell">
|
||||||
|
<div *ngIf="file.isError" class="small-label error" translate="dossier-overview.file-listing.file-entry.file-error"></div>
|
||||||
|
<div *ngIf="file.isPending" class="small-label" translate="dossier-overview.file-listing.file-entry.file-pending"></div>
|
||||||
|
<div *ngIf="file.isProcessing" class="small-label loading" translate="dossier-overview.file-listing.file-entry.file-processing"></div>
|
||||||
|
<iqser-status-bar
|
||||||
|
*ngIf="file.isWorkable"
|
||||||
|
[configs]="[
|
||||||
|
{
|
||||||
|
color: file.status,
|
||||||
|
length: 1
|
||||||
|
}
|
||||||
|
]"
|
||||||
|
></iqser-status-bar>
|
||||||
|
|
||||||
|
<redaction-file-actions
|
||||||
|
(actionPerformed)="calculateData.emit()"
|
||||||
|
*ngIf="!file.isProcessing"
|
||||||
|
[file]="file"
|
||||||
|
class="mr-4"
|
||||||
|
type="dossier-overview-list"
|
||||||
|
></redaction-file-actions>
|
||||||
|
</div>
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
@use 'common-mixins';
|
||||||
|
@use 'variables';
|
||||||
|
|
||||||
|
.cell {
|
||||||
|
.error {
|
||||||
|
color: variables.$primary;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-item-title {
|
||||||
|
max-width: 25vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.primary-attribute {
|
||||||
|
padding-top: 6px;
|
||||||
|
@include common-mixins.line-clamp(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.extend-cols {
|
||||||
|
grid-column-end: span 3;
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.status-container {
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output, TemplateRef } from '@angular/core';
|
||||||
|
import { File } from '@models/file/file';
|
||||||
|
import { Required } from '@iqser/common-ui';
|
||||||
|
import { IFileAttributeConfig } from '@redaction/red-ui-http';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'redaction-table-item',
|
||||||
|
templateUrl: './table-item.component.html',
|
||||||
|
styleUrls: ['./table-item.component.scss'],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
|
})
|
||||||
|
export class TableItemComponent {
|
||||||
|
@Input() @Required() file!: File;
|
||||||
|
@Input() @Required() statsTemplate!: TemplateRef<unknown>;
|
||||||
|
@Input() @Required() displayedAttributes!: IFileAttributeConfig[];
|
||||||
|
@Output() readonly calculateData = new EventEmitter<void>();
|
||||||
|
|
||||||
|
constructor() {}
|
||||||
|
}
|
||||||
@ -0,0 +1,364 @@
|
|||||||
|
import { Injectable, TemplateRef } from '@angular/core';
|
||||||
|
import { IFilterGroup, INestedFilter, keyChecker, LoadingService, NestedFilter, TableColumnConfig, WorkflowConfig } from '@iqser/common-ui';
|
||||||
|
import { File } from '@models/file/file';
|
||||||
|
import { fileStatusTranslations } from '../../translations/file-status-translations';
|
||||||
|
import { FileStatus, FileStatuses, IFileAttributeConfig } from '@redaction/red-ui-http';
|
||||||
|
import { FileActionService } from '../../shared/services/file-action.service';
|
||||||
|
import { AppStateService } from '@state/app-state.service';
|
||||||
|
import { PermissionsService } from '@services/permissions.service';
|
||||||
|
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { UserService } from '@services/user.service';
|
||||||
|
import { DossiersDialogService } from '../../services/dossiers-dialog.service';
|
||||||
|
import { annotationFilterChecker, RedactionFilterSorter, StatusSorter } from '@utils/index';
|
||||||
|
import { workloadTranslations } from '../../translations/workload-translations';
|
||||||
|
import * as moment from 'moment';
|
||||||
|
import { ConfigService as AppConfigService } from '@services/config.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ConfigService {
|
||||||
|
constructor(
|
||||||
|
private readonly _fileActionService: FileActionService,
|
||||||
|
private readonly _loadingService: LoadingService,
|
||||||
|
private readonly _appStateService: AppStateService,
|
||||||
|
private readonly _permissionsService: PermissionsService,
|
||||||
|
private readonly _translateService: TranslateService,
|
||||||
|
private readonly _userService: UserService,
|
||||||
|
private readonly _dialogService: DossiersDialogService,
|
||||||
|
private readonly _appConfigService: AppConfigService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
get actionConfig() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: this._translateService.instant('dossier-overview.header-actions.edit'),
|
||||||
|
action: $event => this._openEditDossierDialog($event),
|
||||||
|
icon: 'iqser:edit',
|
||||||
|
hide: !this._userService.currentUser.isManager
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
tableConfig(displayedAttributes: IFileAttributeConfig[]): TableColumnConfig<File>[] {
|
||||||
|
const dynamicColumns: TableColumnConfig<File>[] = displayedAttributes.map(config => ({
|
||||||
|
label: config.label,
|
||||||
|
notTranslatable: true
|
||||||
|
}));
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: _('dossier-overview.table-col-names.name'),
|
||||||
|
sortByKey: 'searchKey',
|
||||||
|
width: '3fr'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: _('dossier-overview.table-col-names.added-on'),
|
||||||
|
sortByKey: 'added',
|
||||||
|
width: '2fr'
|
||||||
|
},
|
||||||
|
...dynamicColumns,
|
||||||
|
{
|
||||||
|
label: _('dossier-overview.table-col-names.needs-work')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: _('dossier-overview.table-col-names.assigned-to'),
|
||||||
|
class: 'user-column',
|
||||||
|
sortByKey: 'reviewerName',
|
||||||
|
width: '2fr'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: _('dossier-overview.table-col-names.pages'),
|
||||||
|
sortByKey: 'numberOfPages'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: _('dossier-overview.table-col-names.status'),
|
||||||
|
class: 'flex-end',
|
||||||
|
sortByKey: 'statusSort'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
workflowConfig(reloadDossiers: () => Promise<void>): WorkflowConfig<File, FileStatus> {
|
||||||
|
return {
|
||||||
|
columnIdentifierFn: entity => entity.status,
|
||||||
|
itemVersionFn: (entity: File) => `${entity.lastUpdated}-${entity.numberOfAnalyses}`,
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
label: fileStatusTranslations[FileStatuses.UNASSIGNED],
|
||||||
|
key: FileStatuses.UNASSIGNED,
|
||||||
|
enterFn: this._unassignFn(reloadDossiers),
|
||||||
|
enterPredicate: () => false,
|
||||||
|
color: '#D3D5DA'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: fileStatusTranslations[FileStatuses.UNDER_REVIEW],
|
||||||
|
enterFn: this._underReviewFn(reloadDossiers),
|
||||||
|
enterPredicate: (file: File) =>
|
||||||
|
this._permissionsService.canSetUnderReview(file) ||
|
||||||
|
this._permissionsService.canAssignToSelf(file) ||
|
||||||
|
this._permissionsService.canAssignUser(file),
|
||||||
|
key: FileStatuses.UNDER_REVIEW,
|
||||||
|
color: '#FDBD00'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: fileStatusTranslations[FileStatuses.UNDER_APPROVAL],
|
||||||
|
enterFn: this._underApprovalFn(reloadDossiers),
|
||||||
|
enterPredicate: (file: File) =>
|
||||||
|
this._permissionsService.canSetUnderApproval(file) || this._permissionsService.canUndoApproval(file),
|
||||||
|
key: FileStatuses.UNDER_APPROVAL,
|
||||||
|
color: '#374C81'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: fileStatusTranslations[FileStatuses.APPROVED],
|
||||||
|
enterFn: this._approveFn(reloadDossiers),
|
||||||
|
enterPredicate: (file: File) => this._permissionsService.isReadyForApproval(file) && file.canBeApproved,
|
||||||
|
key: FileStatuses.APPROVED,
|
||||||
|
color: '#48C9F7'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
filterGroups(
|
||||||
|
entities: File[],
|
||||||
|
fileAttributeConfigs: IFileAttributeConfig[],
|
||||||
|
needsWorkFilterTemplate: TemplateRef<unknown>,
|
||||||
|
checkedRequiredFilters: () => NestedFilter[],
|
||||||
|
checkedNotRequiredFilters: () => NestedFilter[]
|
||||||
|
) {
|
||||||
|
const allDistinctFileStatuses = new Set<string>();
|
||||||
|
const allDistinctPeople = new Set<string>();
|
||||||
|
const allDistinctAddedDates = new Set<string>();
|
||||||
|
const allDistinctNeedsWork = new Set<string>();
|
||||||
|
|
||||||
|
const dynamicFilters = new Map<string, Set<string>>();
|
||||||
|
|
||||||
|
const filterGroups: IFilterGroup[] = [];
|
||||||
|
|
||||||
|
entities.forEach(file => {
|
||||||
|
allDistinctPeople.add(file.currentReviewer);
|
||||||
|
allDistinctFileStatuses.add(file.status);
|
||||||
|
allDistinctAddedDates.add(moment(file.added).format('DD/MM/YYYY'));
|
||||||
|
|
||||||
|
if (file.analysisRequired) {
|
||||||
|
allDistinctNeedsWork.add('analysis');
|
||||||
|
}
|
||||||
|
if (file.hintsOnly) {
|
||||||
|
allDistinctNeedsWork.add('hint');
|
||||||
|
}
|
||||||
|
if (file.hasRedactions) {
|
||||||
|
allDistinctNeedsWork.add('redaction');
|
||||||
|
}
|
||||||
|
if (file.hasSuggestions) {
|
||||||
|
allDistinctNeedsWork.add('suggestion');
|
||||||
|
}
|
||||||
|
if (file.hasUpdates) {
|
||||||
|
allDistinctNeedsWork.add('updated');
|
||||||
|
}
|
||||||
|
if (file.hasImages) {
|
||||||
|
allDistinctNeedsWork.add('image');
|
||||||
|
}
|
||||||
|
if (file.hasNone) {
|
||||||
|
allDistinctNeedsWork.add('none');
|
||||||
|
}
|
||||||
|
if (file.hasAnnotationComments) {
|
||||||
|
allDistinctNeedsWork.add('comment');
|
||||||
|
}
|
||||||
|
|
||||||
|
// extract values for dynamic filters
|
||||||
|
fileAttributeConfigs.forEach(config => {
|
||||||
|
if (config.filterable) {
|
||||||
|
const filterKey = `${config.id}:${config.label}`;
|
||||||
|
let filters = dynamicFilters.get(filterKey);
|
||||||
|
if (!filters) {
|
||||||
|
dynamicFilters.set(filterKey, new Set<string>());
|
||||||
|
filters = dynamicFilters.get(filterKey);
|
||||||
|
}
|
||||||
|
let filterValue = file.fileAttributes?.attributeIdToValue[config.id];
|
||||||
|
if (!filterValue) {
|
||||||
|
filterValue = '-';
|
||||||
|
file.fileAttributes.attributeIdToValue[config.id] = '-';
|
||||||
|
}
|
||||||
|
filters.add(filterValue);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const statusFilters = [...allDistinctFileStatuses].map(
|
||||||
|
status =>
|
||||||
|
new NestedFilter({
|
||||||
|
id: status,
|
||||||
|
label: this._translateService.instant(fileStatusTranslations[status])
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
filterGroups.push({
|
||||||
|
slug: 'statusFilters',
|
||||||
|
label: this._translateService.instant('filters.status'),
|
||||||
|
icon: 'red:status',
|
||||||
|
filters: statusFilters.sort((a, b) => StatusSorter[a.id] - StatusSorter[b.id]),
|
||||||
|
checker: keyChecker('status')
|
||||||
|
});
|
||||||
|
|
||||||
|
const peopleFilters: NestedFilter[] = [];
|
||||||
|
if (allDistinctPeople.has(undefined) || allDistinctPeople.has(null)) {
|
||||||
|
allDistinctPeople.delete(undefined);
|
||||||
|
allDistinctPeople.delete(null);
|
||||||
|
peopleFilters.push(
|
||||||
|
new NestedFilter({
|
||||||
|
id: null,
|
||||||
|
label: this._translateService.instant('initials-avatar.unassigned')
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
allDistinctPeople.forEach(userId => {
|
||||||
|
peopleFilters.push(
|
||||||
|
new NestedFilter({
|
||||||
|
id: userId,
|
||||||
|
label: this._userService.getNameForId(userId)
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
filterGroups.push({
|
||||||
|
slug: 'peopleFilters',
|
||||||
|
label: this._translateService.instant('filters.assigned-people'),
|
||||||
|
icon: 'red:user',
|
||||||
|
filters: peopleFilters,
|
||||||
|
checker: keyChecker('currentReviewer')
|
||||||
|
});
|
||||||
|
|
||||||
|
const needsWorkFilters = [...allDistinctNeedsWork].map(
|
||||||
|
item =>
|
||||||
|
new NestedFilter({
|
||||||
|
id: item,
|
||||||
|
label: workloadTranslations[item]
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
filterGroups.push({
|
||||||
|
slug: 'needsWorkFilters',
|
||||||
|
label: this._translateService.instant('filters.needs-work'),
|
||||||
|
icon: 'red:needs-work',
|
||||||
|
filterTemplate: needsWorkFilterTemplate,
|
||||||
|
filters: needsWorkFilters.sort(RedactionFilterSorter.byKey),
|
||||||
|
checker: annotationFilterChecker,
|
||||||
|
matchAll: true
|
||||||
|
});
|
||||||
|
|
||||||
|
dynamicFilters.forEach((filterValue: Set<string>, filterKey: string) => {
|
||||||
|
const id = filterKey.split(':')[0];
|
||||||
|
const key = filterKey.split(':')[1];
|
||||||
|
filterGroups.push({
|
||||||
|
slug: key,
|
||||||
|
label: key,
|
||||||
|
icon: 'red:template',
|
||||||
|
filters: [...filterValue].map(
|
||||||
|
(value: string) =>
|
||||||
|
new NestedFilter({
|
||||||
|
id: value,
|
||||||
|
label: value === '-' ? this._translateService.instant('filters.empty') : value
|
||||||
|
})
|
||||||
|
),
|
||||||
|
checker: (input: File, filter: INestedFilter) => filter.id === input.fileAttributes.attributeIdToValue[id]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
filterGroups.push({
|
||||||
|
slug: 'quickFilters',
|
||||||
|
filters: this._quickFilters(entities),
|
||||||
|
checker: (file: File) =>
|
||||||
|
checkedRequiredFilters().reduce((acc, f) => acc && f.checker(file), true) &&
|
||||||
|
(checkedNotRequiredFilters().length === 0 || checkedNotRequiredFilters().reduce((acc, f) => acc || f.checker(file), false))
|
||||||
|
});
|
||||||
|
|
||||||
|
const filesNamesFilters = entities.map(
|
||||||
|
file =>
|
||||||
|
new NestedFilter({
|
||||||
|
id: file.filename,
|
||||||
|
label: file.filename
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
filterGroups.push({
|
||||||
|
slug: 'filesNamesFilter',
|
||||||
|
label: this._translateService.instant('dossier-overview.filters.label'),
|
||||||
|
icon: 'red:document',
|
||||||
|
filters: filesNamesFilters,
|
||||||
|
checker: keyChecker('filename'),
|
||||||
|
filterceptionPlaceholder: this._translateService.instant('dossier-overview.filters.search')
|
||||||
|
});
|
||||||
|
|
||||||
|
return filterGroups;
|
||||||
|
}
|
||||||
|
|
||||||
|
_recentlyModifiedChecker = (file: File) =>
|
||||||
|
moment(file.lastUpdated).add(this._appConfigService.values.RECENT_PERIOD_IN_HOURS, 'hours').isAfter(moment());
|
||||||
|
|
||||||
|
private _quickFilters(entities: File[]): NestedFilter[] {
|
||||||
|
let quickFilters: INestedFilter[] = [];
|
||||||
|
if (entities.filter(this._recentlyModifiedChecker).length > 0) {
|
||||||
|
const recentPeriod = this._appConfigService.values.RECENT_PERIOD_IN_HOURS;
|
||||||
|
quickFilters = [
|
||||||
|
{
|
||||||
|
id: 'recent',
|
||||||
|
label: this._translateService.instant('dossier-overview.quick-filters.recent', {
|
||||||
|
hours: recentPeriod
|
||||||
|
}),
|
||||||
|
required: true,
|
||||||
|
checker: this._recentlyModifiedChecker
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
...quickFilters,
|
||||||
|
{
|
||||||
|
id: 'assigned-to-me',
|
||||||
|
label: this._translateService.instant('dossier-overview.quick-filters.assigned-to-me'),
|
||||||
|
checker: (file: File) => file.currentReviewer === this._userService.currentUser.id
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'unassigned',
|
||||||
|
label: this._translateService.instant('dossier-overview.quick-filters.unassigned'),
|
||||||
|
checker: (file: File) => !file.currentReviewer
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'assigned-to-others',
|
||||||
|
label: this._translateService.instant('dossier-overview.quick-filters.assigned-to-others'),
|
||||||
|
checker: (file: File) => !!file.currentReviewer && file.currentReviewer !== this._userService.currentUser.id
|
||||||
|
}
|
||||||
|
].map(filter => new NestedFilter(filter));
|
||||||
|
}
|
||||||
|
|
||||||
|
private _openEditDossierDialog($event: MouseEvent) {
|
||||||
|
this._dialogService.openDialog('editDossier', $event, {
|
||||||
|
dossier: this._appStateService.activeDossier
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _unassignFn = (reloadDossiers: () => Promise<void>) => async (file: File) => {
|
||||||
|
// TODO
|
||||||
|
console.log('unassign', file);
|
||||||
|
};
|
||||||
|
|
||||||
|
private _underReviewFn = (reloadDossiers: () => Promise<void>) => (file: File) => {
|
||||||
|
this._fileActionService.assignFile('reviewer', null, file, () => this._loadingService.loadWhile(reloadDossiers()), true);
|
||||||
|
};
|
||||||
|
|
||||||
|
private _underApprovalFn = (reloadDossiers: () => Promise<void>) => async (file: File) => {
|
||||||
|
if (this._appStateService.activeDossier.approverIds.length > 1) {
|
||||||
|
this._fileActionService.assignFile('approver', null, file, () => this._loadingService.loadWhile(reloadDossiers()), true);
|
||||||
|
} else {
|
||||||
|
this._loadingService.start();
|
||||||
|
await this._fileActionService.setFilesUnderApproval([file]).toPromise();
|
||||||
|
await reloadDossiers();
|
||||||
|
this._loadingService.stop();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private _approveFn = (reloadDossiers: () => Promise<void>) => async (file: File) => {
|
||||||
|
this._loadingService.start();
|
||||||
|
await this._fileActionService.setFilesApproved([file]).toPromise();
|
||||||
|
await reloadDossiers();
|
||||||
|
this._loadingService.stop();
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -0,0 +1,34 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { RouterModule } from '@angular/router';
|
||||||
|
import { SharedModule } from '@shared/shared.module';
|
||||||
|
import { IqserIconsModule } from '@iqser/common-ui';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { DossierOverviewScreenComponent } from './screen/dossier-overview-screen.component';
|
||||||
|
import { DossierOverviewBulkActionsComponent } from './components/bulk-actions/dossier-overview-bulk-actions.component';
|
||||||
|
import { DossierDetailsComponent } from './components/dossier-details/dossier-details.component';
|
||||||
|
import { DossierDetailsStatsComponent } from './components/dossier-details-stats/dossier-details-stats.component';
|
||||||
|
import { TableItemComponent } from './components/table-item/table-item.component';
|
||||||
|
import { ConfigService } from './config.service';
|
||||||
|
import { SharedDossiersModule } from '../../shared/shared-dossiers.module';
|
||||||
|
|
||||||
|
const routes = [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
component: DossierOverviewScreenComponent,
|
||||||
|
pathMatch: 'full'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
DossierOverviewScreenComponent,
|
||||||
|
DossierOverviewBulkActionsComponent,
|
||||||
|
DossierDetailsComponent,
|
||||||
|
DossierDetailsStatsComponent,
|
||||||
|
TableItemComponent
|
||||||
|
],
|
||||||
|
providers: [ConfigService],
|
||||||
|
imports: [RouterModule.forChild(routes), CommonModule, SharedModule, SharedDossiersModule, IqserIconsModule, TranslateModule]
|
||||||
|
})
|
||||||
|
export class DossierOverviewModule {}
|
||||||
@ -91,95 +91,6 @@
|
|||||||
<redaction-dossier-overview-bulk-actions (reload)="bulkActionPerformed()"></redaction-dossier-overview-bulk-actions>
|
<redaction-dossier-overview-bulk-actions (reload)="bulkActionPerformed()"></redaction-dossier-overview-bulk-actions>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
<ng-template #filenameTemplate let-file="entity">
|
|
||||||
<div class="cell">
|
|
||||||
<div>
|
|
||||||
<div [class.error]="file.isError" [matTooltip]="file.filename" class="table-item-title" matTooltipPosition="above">
|
|
||||||
{{ file.filename }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div *ngIf="file.primaryAttribute" class="small-label">
|
|
||||||
<div class="primary-attribute">
|
|
||||||
<span [matTooltip]="file.primaryAttribute" matTooltipPosition="above">
|
|
||||||
{{ file.primaryAttribute }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<ng-container *ngTemplateOutlet="statsTemplate; context: { entity: file }"></ng-container>
|
|
||||||
</div>
|
|
||||||
</ng-template>
|
|
||||||
|
|
||||||
<ng-template #addedOnTemplate let-file="entity">
|
|
||||||
<div class="cell">
|
|
||||||
<div [class.error]="file.isError" class="small-label">
|
|
||||||
{{ file.added | date: 'd MMM. yyyy, hh:mm a' }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</ng-template>
|
|
||||||
|
|
||||||
<ng-template #attributeTemplate let-config="extra" let-file="entity">
|
|
||||||
<div class="cell">
|
|
||||||
{{ file.fileAttributes.attributeIdToValue[config.id] }}
|
|
||||||
</div>
|
|
||||||
</ng-template>
|
|
||||||
|
|
||||||
<ng-template #needsWorkTemplate let-file="entity">
|
|
||||||
<!-- always show A for error-->
|
|
||||||
<div *ngIf="file.isError" class="cell">
|
|
||||||
<redaction-annotation-icon color="#dd4d50" label="A" type="square"></redaction-annotation-icon>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div *ngIf="!file.isError" class="cell">
|
|
||||||
<redaction-needs-work-badge [needsWorkInput]="file"></redaction-needs-work-badge>
|
|
||||||
</div>
|
|
||||||
</ng-template>
|
|
||||||
|
|
||||||
<ng-template #reviewerTemplate let-file="entity">
|
|
||||||
<div *ngIf="!file.isError" class="user-column cell">
|
|
||||||
<redaction-initials-avatar [userId]="file.currentReviewer" [withName]="true"></redaction-initials-avatar>
|
|
||||||
</div>
|
|
||||||
</ng-template>
|
|
||||||
|
|
||||||
<ng-template #pagesTemplate let-file="entity">
|
|
||||||
<div *ngIf="!file.isError" class="cell">
|
|
||||||
<div class="small-label stats-subtitle">
|
|
||||||
<div>
|
|
||||||
<mat-icon svgIcon="red:pages"></mat-icon>
|
|
||||||
{{ file.numberOfPages }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</ng-template>
|
|
||||||
|
|
||||||
<ng-template #statusTemplate let-file="entity">
|
|
||||||
<div [class.extend-cols]="file.isError" class="status-container cell">
|
|
||||||
<div *ngIf="file.isError" class="small-label error" translate="dossier-overview.file-listing.file-entry.file-error"></div>
|
|
||||||
<div *ngIf="file.isPending" class="small-label" translate="dossier-overview.file-listing.file-entry.file-pending"></div>
|
|
||||||
<div
|
|
||||||
*ngIf="file.isProcessing"
|
|
||||||
class="small-label loading"
|
|
||||||
translate="dossier-overview.file-listing.file-entry.file-processing"
|
|
||||||
></div>
|
|
||||||
<iqser-status-bar
|
|
||||||
*ngIf="file.isWorkable"
|
|
||||||
[configs]="[
|
|
||||||
{
|
|
||||||
color: file.status,
|
|
||||||
length: 1
|
|
||||||
}
|
|
||||||
]"
|
|
||||||
></iqser-status-bar>
|
|
||||||
|
|
||||||
<redaction-file-actions
|
|
||||||
(actionPerformed)="calculateData()"
|
|
||||||
*ngIf="!file.isProcessing"
|
|
||||||
[file]="file"
|
|
||||||
class="mr-4"
|
|
||||||
type="dossier-overview-list"
|
|
||||||
></redaction-file-actions>
|
|
||||||
</div>
|
|
||||||
</ng-template>
|
|
||||||
|
|
||||||
<ng-template #viewModeSelection>
|
<ng-template #viewModeSelection>
|
||||||
<div class="view-mode-selection">
|
<div class="view-mode-selection">
|
||||||
<div class="all-caps-label" translate="view-mode.view-as"></div>
|
<div class="all-caps-label" translate="view-mode.view-as"></div>
|
||||||
@ -199,6 +110,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
|
<ng-template #tableItemTemplate let-file="entity">
|
||||||
|
<redaction-table-item
|
||||||
|
(calculateData)="calculateData()"
|
||||||
|
[displayedAttributes]="displayedAttributes"
|
||||||
|
[file]="file"
|
||||||
|
[statsTemplate]="statsTemplate"
|
||||||
|
></redaction-table-item>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
<ng-template #workflowItemTemplate let-file="entity">
|
<ng-template #workflowItemTemplate let-file="entity">
|
||||||
<div class="workflow-item">
|
<div class="workflow-item">
|
||||||
<div>
|
<div>
|
||||||
@ -7,39 +7,17 @@
|
|||||||
|
|
||||||
:host ::ng-deep iqser-table cdk-virtual-scroll-viewport .cdk-virtual-scroll-content-wrapper .table-item {
|
:host ::ng-deep iqser-table cdk-virtual-scroll-viewport .cdk-virtual-scroll-content-wrapper .table-item {
|
||||||
&.last-opened {
|
&.last-opened {
|
||||||
> .selection-column {
|
.selection-column {
|
||||||
padding-left: 6px !important;
|
padding-left: 6px !important;
|
||||||
border-left: 4px solid variables.$primary;
|
border-left: 4px solid variables.$primary;
|
||||||
}
|
}
|
||||||
|
|
||||||
> div {
|
.selection-column,
|
||||||
|
.cell,
|
||||||
|
.scrollbar-placeholder {
|
||||||
animation: red-fading-background 3s 1;
|
animation: red-fading-background 3s 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> div.cell {
|
|
||||||
.error {
|
|
||||||
color: variables.$primary;
|
|
||||||
}
|
|
||||||
|
|
||||||
.table-item-title {
|
|
||||||
max-width: 25vw;
|
|
||||||
}
|
|
||||||
|
|
||||||
.primary-attribute {
|
|
||||||
padding-top: 6px;
|
|
||||||
@include common-mixins.line-clamp(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.extend-cols {
|
|
||||||
grid-column-end: span 3;
|
|
||||||
align-items: flex-end;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.status-container {
|
|
||||||
align-items: flex-end;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.right-container {
|
.right-container {
|
||||||
@ -0,0 +1,280 @@
|
|||||||
|
import {
|
||||||
|
ChangeDetectorRef,
|
||||||
|
Component,
|
||||||
|
ElementRef,
|
||||||
|
forwardRef,
|
||||||
|
HostListener,
|
||||||
|
Injector,
|
||||||
|
OnDestroy,
|
||||||
|
OnInit,
|
||||||
|
TemplateRef,
|
||||||
|
ViewChild
|
||||||
|
} from '@angular/core';
|
||||||
|
import { FileStatus, IFileAttributeConfig } from '@redaction/red-ui-http';
|
||||||
|
import { AppStateService } from '@state/app-state.service';
|
||||||
|
import { FileDropOverlayService } from '@upload-download/services/file-drop-overlay.service';
|
||||||
|
import { FileUploadModel } from '@upload-download/model/file-upload.model';
|
||||||
|
import { FileUploadService } from '@upload-download/services/file-upload.service';
|
||||||
|
import { StatusOverlayService } from '@upload-download/services/status-overlay.service';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import * as moment from 'moment';
|
||||||
|
import { DossierDetailsComponent } from '../components/dossier-details/dossier-details.component';
|
||||||
|
import { File } from '@models/file/file';
|
||||||
|
import { UserService } from '@services/user.service';
|
||||||
|
import { timer } from 'rxjs';
|
||||||
|
import { tap } from 'rxjs/operators';
|
||||||
|
import { convertFiles, Files, handleFileDrop, OnAttach, OnDetach } from '@utils/index';
|
||||||
|
import { DossiersDialogService } from '../../../services/dossiers-dialog.service';
|
||||||
|
import { ActionConfig } from '@shared/components/page-header/models/action-config.model';
|
||||||
|
import {
|
||||||
|
CircleButtonTypes,
|
||||||
|
DefaultListingServices,
|
||||||
|
ListingComponent,
|
||||||
|
ListingModes,
|
||||||
|
LoadingService,
|
||||||
|
NestedFilter,
|
||||||
|
TableColumnConfig,
|
||||||
|
TableComponent,
|
||||||
|
Toaster,
|
||||||
|
WorkflowConfig
|
||||||
|
} from '@iqser/common-ui';
|
||||||
|
import { DossierAttributesService } from '@shared/services/controller-wrappers/dossier-attributes.service';
|
||||||
|
import { DossierAttributeWithValue } from '@models/dossier-attributes.model';
|
||||||
|
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||||
|
import { PermissionsService } from '@services/permissions.service';
|
||||||
|
import { RouterHistoryService } from '@services/router-history.service';
|
||||||
|
import { Dossier } from '@state/model/dossier';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { FileAttributesService } from '../../../services/file-attributes.service';
|
||||||
|
import { ConfigService as AppConfigService } from '@services/config.service';
|
||||||
|
import { ConfigService } from '../config.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
templateUrl: './dossier-overview-screen.component.html',
|
||||||
|
styleUrls: ['./dossier-overview-screen.component.scss'],
|
||||||
|
providers: [...DefaultListingServices, { provide: ListingComponent, useExisting: forwardRef(() => DossierOverviewScreenComponent) }]
|
||||||
|
})
|
||||||
|
export class DossierOverviewScreenComponent extends ListingComponent<File> implements OnInit, OnDestroy, OnDetach, OnAttach {
|
||||||
|
readonly listingModes = ListingModes;
|
||||||
|
readonly circleButtonTypes = CircleButtonTypes;
|
||||||
|
readonly currentUser = this._userService.currentUser;
|
||||||
|
currentDossier = this._appStateService.activeDossier;
|
||||||
|
readonly tableHeaderLabel = _('dossier-overview.table-header.title');
|
||||||
|
collapsedDetails = false;
|
||||||
|
dossierAttributes: DossierAttributeWithValue[] = [];
|
||||||
|
fileAttributeConfigs: IFileAttributeConfig[];
|
||||||
|
tableColumnConfigs: readonly TableColumnConfig<File>[];
|
||||||
|
readonly workflowConfig: WorkflowConfig<File, FileStatus> = this._configService.workflowConfig(() => this.reloadDossiers());
|
||||||
|
readonly actionConfigs: readonly ActionConfig[] = this._configService.actionConfig;
|
||||||
|
@ViewChild(DossierDetailsComponent, { static: false }) private readonly _dossierDetailsComponent: DossierDetailsComponent;
|
||||||
|
private _lastScrolledIndex: number;
|
||||||
|
@ViewChild('needsWorkFilterTemplate', { read: TemplateRef, static: true })
|
||||||
|
private readonly _needsWorkFilterTemplate: TemplateRef<unknown>;
|
||||||
|
@ViewChild('fileInput') private readonly _fileInput: ElementRef;
|
||||||
|
@ViewChild(TableComponent) private readonly _tableComponent: TableComponent<Dossier>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly _toaster: Toaster,
|
||||||
|
protected readonly _injector: Injector,
|
||||||
|
private readonly _router: Router,
|
||||||
|
private readonly _userService: UserService,
|
||||||
|
readonly permissionsService: PermissionsService,
|
||||||
|
private readonly _loadingService: LoadingService,
|
||||||
|
private readonly _appStateService: AppStateService,
|
||||||
|
readonly routerHistoryService: RouterHistoryService,
|
||||||
|
private readonly _appConfigService: AppConfigService,
|
||||||
|
private readonly _translateService: TranslateService,
|
||||||
|
private readonly _dialogService: DossiersDialogService,
|
||||||
|
private readonly _changeDetectorRef: ChangeDetectorRef,
|
||||||
|
private readonly _fileUploadService: FileUploadService,
|
||||||
|
private readonly _statusOverlayService: StatusOverlayService,
|
||||||
|
private readonly _fileDropOverlayService: FileDropOverlayService,
|
||||||
|
private readonly _dossierAttributesService: DossierAttributesService,
|
||||||
|
private readonly _fileAttributesService: FileAttributesService,
|
||||||
|
private readonly _configService: ConfigService
|
||||||
|
) {
|
||||||
|
super(_injector);
|
||||||
|
this._loadEntitiesFromState();
|
||||||
|
this.fileAttributeConfigs = this._fileAttributesService.getFileAttributeConfig(
|
||||||
|
this.currentDossier.dossierTemplateId
|
||||||
|
)?.fileAttributeConfigs;
|
||||||
|
this.tableColumnConfigs = this._configService.tableConfig(this.displayedAttributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
get checkedRequiredFilters(): NestedFilter[] {
|
||||||
|
return this.filterService.getGroup('quickFilters')?.filters.filter(f => f.required && f.checked);
|
||||||
|
}
|
||||||
|
|
||||||
|
get checkedNotRequiredFilters(): NestedFilter[] {
|
||||||
|
return this.filterService.getGroup('quickFilters')?.filters.filter(f => !f.required && f.checked);
|
||||||
|
}
|
||||||
|
|
||||||
|
get displayedInFileListAttributes() {
|
||||||
|
return this.fileAttributeConfigs?.filter(config => config.displayedInFileList) || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
get displayedAttributes(): IFileAttributeConfig[] {
|
||||||
|
return this.displayedInFileListAttributes.filter(c => c.displayedInFileList);
|
||||||
|
}
|
||||||
|
|
||||||
|
actionPerformed(action?: string, file?: File) {
|
||||||
|
this.calculateData();
|
||||||
|
|
||||||
|
if (action === 'navigate') {
|
||||||
|
this._router.navigate([file.routerLink]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
disabledFn = (fileStatus: File) => fileStatus.excluded;
|
||||||
|
|
||||||
|
lastOpenedFn = (fileStatus: File) => fileStatus.lastOpened;
|
||||||
|
|
||||||
|
async ngOnInit(): Promise<void> {
|
||||||
|
this._loadingService.start();
|
||||||
|
try {
|
||||||
|
this._fileDropOverlayService.initFileDropHandling();
|
||||||
|
|
||||||
|
this.calculateData();
|
||||||
|
|
||||||
|
this.addSubscription = timer(0, 7500).subscribe(async () => {
|
||||||
|
await this._appStateService.reloadActiveDossierFilesIfNecessary();
|
||||||
|
this._loadEntitiesFromState();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.addSubscription = this._appStateService.fileChanged$.subscribe(() => {
|
||||||
|
this.calculateData();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.addSubscription = this._appStateService.dossierTemplateChanged$.subscribe(() => {
|
||||||
|
this.fileAttributeConfigs = this._fileAttributesService.getFileAttributeConfig(
|
||||||
|
this.currentDossier.dossierTemplateId
|
||||||
|
)?.fileAttributeConfigs;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.addSubscription = this._tableComponent.scrollViewport.scrolledIndexChange
|
||||||
|
.pipe(tap(index => (this._lastScrolledIndex = index)))
|
||||||
|
.subscribe();
|
||||||
|
|
||||||
|
this.dossierAttributes = await this._dossierAttributesService.getValues(this.currentDossier);
|
||||||
|
} catch (e) {
|
||||||
|
console.log('Error from dossier overview screen: ', e);
|
||||||
|
} finally {
|
||||||
|
this._loadingService.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this._fileDropOverlayService.cleanupFileDropHandling();
|
||||||
|
super.ngOnDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
async ngOnAttach() {
|
||||||
|
await this._appStateService.reloadActiveDossierFiles();
|
||||||
|
this._loadEntitiesFromState();
|
||||||
|
await this.ngOnInit();
|
||||||
|
this._tableComponent.scrollViewport.scrollToIndex(this._lastScrolledIndex, 'smooth');
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDetach() {
|
||||||
|
this.ngOnDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
async reanalyseDossier() {
|
||||||
|
try {
|
||||||
|
await this._appStateService.reanalyzeDossier();
|
||||||
|
await this.reloadDossiers();
|
||||||
|
this._toaster.success(_('dossier-overview.reanalyse-dossier.success'));
|
||||||
|
} catch (e) {
|
||||||
|
this._toaster.error(_('dossier-overview.reanalyse-dossier.error'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async reloadDossiers() {
|
||||||
|
await this._appStateService.getFiles(this.currentDossier, false);
|
||||||
|
this.calculateData();
|
||||||
|
}
|
||||||
|
|
||||||
|
calculateData(): void {
|
||||||
|
if (!this._appStateService.activeDossierId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._loadEntitiesFromState();
|
||||||
|
this._computeAllFilters();
|
||||||
|
|
||||||
|
this._dossierDetailsComponent?.calculateChartConfig();
|
||||||
|
this._changeDetectorRef.detectChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
@HostListener('drop', ['$event'])
|
||||||
|
onDrop(event: DragEvent): void {
|
||||||
|
handleFileDrop(event, this.currentDossier, this._uploadFiles.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
@HostListener('dragover', ['$event'])
|
||||||
|
onDragOver(event): void {
|
||||||
|
event.stopPropagation();
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
async uploadFiles(files: Files): Promise<void> {
|
||||||
|
await this._uploadFiles(convertFiles(files, this.currentDossier));
|
||||||
|
this._fileInput.nativeElement.value = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async bulkActionPerformed(): Promise<void> {
|
||||||
|
this.entitiesService.setSelected([]);
|
||||||
|
await this.reloadDossiers();
|
||||||
|
}
|
||||||
|
|
||||||
|
openAssignDossierMembersDialog(): void {
|
||||||
|
const data = { dossier: this.currentDossier, section: 'members' };
|
||||||
|
this._dialogService.openDialog('editDossier', null, data, async () => await this.reloadDossiers());
|
||||||
|
}
|
||||||
|
|
||||||
|
openDossierDictionaryDialog() {
|
||||||
|
const data = { dossier: this.currentDossier, section: 'dossierDictionary' };
|
||||||
|
this._dialogService.openDialog('editDossier', null, data, async () => {
|
||||||
|
await this.reloadDossiers();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleCollapsedDetails() {
|
||||||
|
this.collapsedDetails = !this.collapsedDetails;
|
||||||
|
}
|
||||||
|
|
||||||
|
recentlyModifiedChecker = (file: File) =>
|
||||||
|
moment(file.lastUpdated).add(this._appConfigService.values.RECENT_PERIOD_IN_HOURS, 'hours').isAfter(moment());
|
||||||
|
|
||||||
|
private _loadEntitiesFromState() {
|
||||||
|
this.currentDossier = this._appStateService.activeDossier;
|
||||||
|
if (this.currentDossier) {
|
||||||
|
this.entitiesService.setEntities(this.currentDossier.files);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _uploadFiles(files: FileUploadModel[]) {
|
||||||
|
const fileCount = await this._fileUploadService.uploadFiles(files);
|
||||||
|
if (fileCount) {
|
||||||
|
this._statusOverlayService.openUploadStatusOverlay();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _computeAllFilters() {
|
||||||
|
if (!this.currentDossier) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const filterGroups = this._configService.filterGroups(
|
||||||
|
this.entitiesService.all,
|
||||||
|
this.fileAttributeConfigs,
|
||||||
|
this._needsWorkFilterTemplate,
|
||||||
|
() => this.checkedRequiredFilters,
|
||||||
|
() => this.checkedNotRequiredFilters
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const filterGroup of filterGroups) {
|
||||||
|
this.filterService.addFilterGroup(filterGroup);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,19 +1,19 @@
|
|||||||
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
|
||||||
import { PermissionsService } from '@services/permissions.service';
|
import { PermissionsService } from '@services/permissions.service';
|
||||||
import { Dossier } from '../../../../state/model/dossier';
|
|
||||||
import { StatusSorter } from '@utils/sorters/status-sorter';
|
import { StatusSorter } from '@utils/sorters/status-sorter';
|
||||||
import { AppStateService } from '@state/app-state.service';
|
|
||||||
import { DossiersDialogService } from '../../services/dossiers-dialog.service';
|
|
||||||
import { CircleButtonTypes, StatusBarConfig } from '@iqser/common-ui';
|
import { CircleButtonTypes, StatusBarConfig } from '@iqser/common-ui';
|
||||||
import { UserService } from '@services/user.service';
|
import { UserService } from '@services/user.service';
|
||||||
|
import { AppStateService } from '@state/app-state.service';
|
||||||
|
import { Dossier } from '@state/model/dossier';
|
||||||
|
import { DossiersDialogService } from '../../../../services/dossiers-dialog.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'redaction-dossier-listing-actions',
|
selector: 'redaction-dossiers-listing-actions',
|
||||||
templateUrl: './dossier-listing-actions.component.html',
|
templateUrl: './dossiers-listing-actions.component.html',
|
||||||
styleUrls: ['./dossier-listing-actions.component.scss'],
|
styleUrls: ['./dossiers-listing-actions.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
})
|
})
|
||||||
export class DossierListingActionsComponent {
|
export class DossiersListingActionsComponent {
|
||||||
readonly circleButtonTypes = CircleButtonTypes;
|
readonly circleButtonTypes = CircleButtonTypes;
|
||||||
readonly currentUser = this._userService.currentUser;
|
readonly currentUser = this._userService.currentUser;
|
||||||
|
|
||||||
@ -27,20 +27,6 @@ export class DossierListingActionsComponent {
|
|||||||
private readonly _userService: UserService
|
private readonly _userService: UserService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
openEditDossierDialog($event: MouseEvent, dossier: Dossier): void {
|
|
||||||
this._dialogService.openDialog('editDossier', $event, {
|
|
||||||
dossier,
|
|
||||||
afterSave: () => this.actionPerformed.emit()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
reanalyseDossier($event: MouseEvent, dossier: Dossier): void {
|
|
||||||
$event.stopPropagation();
|
|
||||||
this.appStateService.reanalyzeDossier(dossier).then(() => {
|
|
||||||
this.appStateService.loadAllDossiers().then(() => this.actionPerformed.emit());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
get statusConfig(): readonly StatusBarConfig<string>[] {
|
get statusConfig(): readonly StatusBarConfig<string>[] {
|
||||||
if (!this.dossier) {
|
if (!this.dossier) {
|
||||||
return [];
|
return [];
|
||||||
@ -60,4 +46,18 @@ export class DossierListingActionsComponent {
|
|||||||
.sort(StatusSorter.byStatus)
|
.sort(StatusSorter.byStatus)
|
||||||
.map(status => ({ length: obj[status], color: status }));
|
.map(status => ({ length: obj[status], color: status }));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
openEditDossierDialog($event: MouseEvent, dossier: Dossier): void {
|
||||||
|
this._dialogService.openDialog('editDossier', $event, {
|
||||||
|
dossier,
|
||||||
|
afterSave: () => this.actionPerformed.emit()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
reanalyseDossier($event: MouseEvent, dossier: Dossier): void {
|
||||||
|
$event.stopPropagation();
|
||||||
|
this.appStateService.reanalyzeDossier(dossier).then(() => {
|
||||||
|
this.appStateService.loadAllDossiers().then(() => this.actionPerformed.emit());
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -4,12 +4,12 @@ import { AppStateService } from '@state/app-state.service';
|
|||||||
import { FilterService } from '@iqser/common-ui';
|
import { FilterService } from '@iqser/common-ui';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'redaction-dossier-listing-details',
|
selector: 'redaction-dossiers-listing-details',
|
||||||
templateUrl: './dossier-listing-details.component.html',
|
templateUrl: './dossiers-listing-details.component.html',
|
||||||
styleUrls: ['./dossier-listing-details.component.scss'],
|
styleUrls: ['./dossiers-listing-details.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
})
|
})
|
||||||
export class DossierListingDetailsComponent {
|
export class DossiersListingDetailsComponent {
|
||||||
@Input() dossiersChartData: DoughnutChartConfig[];
|
@Input() dossiersChartData: DoughnutChartConfig[];
|
||||||
@Input() documentsChartData: DoughnutChartConfig[];
|
@Input() documentsChartData: DoughnutChartConfig[];
|
||||||
|
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
<div [matTooltip]="dossier.dossierName" class="table-item-title heading mb-6" matTooltipPosition="above">
|
||||||
|
{{ dossier.dossierName }}
|
||||||
|
</div>
|
||||||
|
<div class="small-label stats-subtitle mb-6">
|
||||||
|
<div>
|
||||||
|
<mat-icon svgIcon="red:template"></mat-icon>
|
||||||
|
{{ getDossierTemplateNameFor(dossier.dossierTemplateId) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="small-label stats-subtitle">
|
||||||
|
<div>
|
||||||
|
<mat-icon svgIcon="red:document"></mat-icon>
|
||||||
|
{{ dossier.filesLength }}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<mat-icon svgIcon="red:pages"></mat-icon>
|
||||||
|
{{ dossier.totalNumberOfPages }}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<mat-icon svgIcon="red:user"></mat-icon>
|
||||||
|
{{ dossier.memberIds.length }}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<mat-icon svgIcon="red:calendar"></mat-icon>
|
||||||
|
{{ dossier.date | date: 'mediumDate' }}
|
||||||
|
</div>
|
||||||
|
<div *ngIf="dossier.dueDate">
|
||||||
|
<mat-icon svgIcon="red:lightning"></mat-icon>
|
||||||
|
{{ dossier.dueDate | date: 'mediumDate' }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
import { Component, Input } from '@angular/core';
|
||||||
|
import { Dossier } from '@state/model/dossier';
|
||||||
|
import { AppStateService } from '@state/app-state.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'redaction-dossiers-listing-dossier-name',
|
||||||
|
templateUrl: './dossiers-listing-dossier-name.component.html',
|
||||||
|
styleUrls: ['./dossiers-listing-dossier-name.component.scss']
|
||||||
|
})
|
||||||
|
export class DossiersListingDossierNameComponent {
|
||||||
|
@Input() dossier: Dossier;
|
||||||
|
|
||||||
|
constructor(private readonly _appStateService: AppStateService) {}
|
||||||
|
|
||||||
|
getDossierTemplateNameFor(dossierTemplateId: string): string {
|
||||||
|
return this._appStateService.getDossierTemplateById(dossierTemplateId).name;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
<div class="cell">
|
||||||
|
<redaction-dossiers-listing-dossier-name [dossier]="dossier"></redaction-dossiers-listing-dossier-name>
|
||||||
|
</div>
|
||||||
|
<div class="cell">
|
||||||
|
<redaction-needs-work-badge [needsWorkInput]="dossier"></redaction-needs-work-badge>
|
||||||
|
</div>
|
||||||
|
<div class="cell user-column">
|
||||||
|
<redaction-initials-avatar [userId]="dossier.ownerId" [withName]="true"></redaction-initials-avatar>
|
||||||
|
</div>
|
||||||
|
<div class="cell status-container">
|
||||||
|
<redaction-dossiers-listing-actions (actionPerformed)="calculateData.emit()" [dossier]="dossier"></redaction-dossiers-listing-actions>
|
||||||
|
</div>
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
.status-container {
|
||||||
|
width: 160px;
|
||||||
|
}
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||||
|
import { Dossier } from '@state/model/dossier';
|
||||||
|
import { Required } from '@iqser/common-ui';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'redaction-table-item',
|
||||||
|
templateUrl: './table-item.component.html',
|
||||||
|
styleUrls: ['./table-item.component.scss']
|
||||||
|
})
|
||||||
|
export class TableItemComponent {
|
||||||
|
@Input() @Required() dossier!: Dossier;
|
||||||
|
@Output() readonly calculateData = new EventEmitter<void>();
|
||||||
|
|
||||||
|
constructor() {}
|
||||||
|
}
|
||||||
@ -0,0 +1,213 @@
|
|||||||
|
import { Injectable, TemplateRef } from '@angular/core';
|
||||||
|
import { IFilterGroup, keyChecker, NestedFilter, TableColumnConfig } from '@iqser/common-ui';
|
||||||
|
import { Dossier } from '@state/model/dossier';
|
||||||
|
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { UserPreferenceService } from '@services/user-preference.service';
|
||||||
|
import { UserService } from '@services/user.service';
|
||||||
|
import { ButtonConfig } from '@shared/components/page-header/models/button-config.model';
|
||||||
|
import { User } from '@models/user';
|
||||||
|
import { fileStatusTranslations } from '../../translations/file-status-translations';
|
||||||
|
import {
|
||||||
|
annotationFilterChecker,
|
||||||
|
dossierMemberChecker,
|
||||||
|
dossierStatusChecker,
|
||||||
|
dossierTemplateChecker,
|
||||||
|
RedactionFilterSorter,
|
||||||
|
StatusSorter
|
||||||
|
} from '@utils/index';
|
||||||
|
import { workloadTranslations } from '../../translations/workload-translations';
|
||||||
|
import { AppStateService } from '@state/app-state.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ConfigService {
|
||||||
|
constructor(
|
||||||
|
private readonly _translateService: TranslateService,
|
||||||
|
private readonly _userPreferenceService: UserPreferenceService,
|
||||||
|
private readonly _userService: UserService,
|
||||||
|
private readonly _appStateService: AppStateService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
get tableConfig(): TableColumnConfig<Dossier>[] {
|
||||||
|
return [
|
||||||
|
{ label: _('dossier-listing.table-col-names.name'), sortByKey: 'searchKey', width: '2fr' },
|
||||||
|
{ label: _('dossier-listing.table-col-names.needs-work') },
|
||||||
|
{ label: _('dossier-listing.table-col-names.owner'), class: 'user-column' },
|
||||||
|
{ label: _('dossier-listing.table-col-names.status'), class: 'flex-end', width: 'auto' }
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
get _currentUser(): User {
|
||||||
|
return this._userService.currentUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
private get _quickFilters(): NestedFilter[] {
|
||||||
|
const myDossiersLabel = this._translateService.instant('dossier-listing.quick-filters.my-dossiers');
|
||||||
|
const filters = [
|
||||||
|
{
|
||||||
|
id: 'my-dossiers',
|
||||||
|
label: myDossiersLabel,
|
||||||
|
checker: (dw: Dossier) => {
|
||||||
|
console.log(dw.ownerId, this._currentUser.id);
|
||||||
|
return dw.ownerId === this._currentUser.id;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'to-approve',
|
||||||
|
label: this._translateService.instant('dossier-listing.quick-filters.to-approve'),
|
||||||
|
checker: (dw: Dossier) => dw.approverIds.includes(this._currentUser.id)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'to-review',
|
||||||
|
label: this._translateService.instant('dossier-listing.quick-filters.to-review'),
|
||||||
|
checker: (dw: Dossier) => dw.memberIds.includes(this._currentUser.id)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'other',
|
||||||
|
label: this._translateService.instant('dossier-listing.quick-filters.other'),
|
||||||
|
checker: (dw: Dossier) => !dw.memberIds.includes(this._currentUser.id)
|
||||||
|
}
|
||||||
|
].map(filter => new NestedFilter(filter));
|
||||||
|
|
||||||
|
return filters.filter(f => f.label === myDossiersLabel || this._userPreferenceService.areDevFeaturesEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
buttonsConfig(addDossier: () => void): ButtonConfig[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: _('dossier-listing.add-new'),
|
||||||
|
action: addDossier,
|
||||||
|
hide: !this._currentUser.isManager,
|
||||||
|
icon: 'red:plus',
|
||||||
|
type: 'primary'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
filterGroups(entities: Dossier[], needsWorkFilterTemplate: TemplateRef<unknown>) {
|
||||||
|
const allDistinctFileStatus = new Set<string>();
|
||||||
|
const allDistinctPeople = new Set<string>();
|
||||||
|
const allDistinctNeedsWork = new Set<string>();
|
||||||
|
const allDistinctDossierTemplates = new Set<string>();
|
||||||
|
|
||||||
|
const filterGroups: IFilterGroup[] = [];
|
||||||
|
|
||||||
|
entities?.forEach(entry => {
|
||||||
|
// all people
|
||||||
|
entry.memberIds.forEach(f => allDistinctPeople.add(f));
|
||||||
|
// Needs work
|
||||||
|
entry.files.forEach(file => {
|
||||||
|
allDistinctFileStatus.add(file.status);
|
||||||
|
if (file.analysisRequired) {
|
||||||
|
allDistinctNeedsWork.add('analysis');
|
||||||
|
}
|
||||||
|
if (entry.hintsOnly) {
|
||||||
|
allDistinctNeedsWork.add('hint');
|
||||||
|
}
|
||||||
|
if (entry.hasRedactions) {
|
||||||
|
allDistinctNeedsWork.add('redaction');
|
||||||
|
}
|
||||||
|
if (entry.hasSuggestions) {
|
||||||
|
allDistinctNeedsWork.add('suggestion');
|
||||||
|
}
|
||||||
|
if (entry.hasNone) {
|
||||||
|
allDistinctNeedsWork.add('none');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
allDistinctDossierTemplates.add(entry.dossierTemplateId);
|
||||||
|
});
|
||||||
|
|
||||||
|
const statusFilters = [...allDistinctFileStatus].map(
|
||||||
|
status =>
|
||||||
|
new NestedFilter({
|
||||||
|
id: status,
|
||||||
|
label: this._translateService.instant(fileStatusTranslations[status])
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
filterGroups.push({
|
||||||
|
slug: 'statusFilters',
|
||||||
|
label: this._translateService.instant('filters.status'),
|
||||||
|
icon: 'red:status',
|
||||||
|
filters: statusFilters.sort((a, b) => StatusSorter[a.id] - StatusSorter[b.id]),
|
||||||
|
checker: dossierStatusChecker
|
||||||
|
});
|
||||||
|
|
||||||
|
const peopleFilters = [...allDistinctPeople].map(
|
||||||
|
userId =>
|
||||||
|
new NestedFilter({
|
||||||
|
id: userId,
|
||||||
|
label: this._userService.getNameForId(userId)
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
filterGroups.push({
|
||||||
|
slug: 'peopleFilters',
|
||||||
|
label: this._translateService.instant('filters.people'),
|
||||||
|
icon: 'red:user',
|
||||||
|
filters: peopleFilters,
|
||||||
|
checker: dossierMemberChecker
|
||||||
|
});
|
||||||
|
|
||||||
|
const needsWorkFilters = [...allDistinctNeedsWork].map(
|
||||||
|
type =>
|
||||||
|
new NestedFilter({
|
||||||
|
id: type,
|
||||||
|
label: workloadTranslations[type]
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
filterGroups.push({
|
||||||
|
slug: 'needsWorkFilters',
|
||||||
|
label: this._translateService.instant('filters.needs-work'),
|
||||||
|
icon: 'red:needs-work',
|
||||||
|
filterTemplate: needsWorkFilterTemplate,
|
||||||
|
filters: needsWorkFilters.sort((a, b) => RedactionFilterSorter[a.id] - RedactionFilterSorter[b.id]),
|
||||||
|
checker: annotationFilterChecker,
|
||||||
|
matchAll: true
|
||||||
|
});
|
||||||
|
|
||||||
|
const dossierTemplateFilters = [...allDistinctDossierTemplates].map(
|
||||||
|
id =>
|
||||||
|
new NestedFilter({
|
||||||
|
id: id,
|
||||||
|
label: this._appStateService.getDossierTemplateById(id).name
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
filterGroups.push({
|
||||||
|
slug: 'dossierTemplateFilters',
|
||||||
|
label: this._translateService.instant('filters.dossier-templates'),
|
||||||
|
icon: 'red:template',
|
||||||
|
hide: dossierTemplateFilters.length <= 1,
|
||||||
|
filters: dossierTemplateFilters,
|
||||||
|
checker: dossierTemplateChecker
|
||||||
|
});
|
||||||
|
|
||||||
|
const quickFilters = this._quickFilters;
|
||||||
|
filterGroups.push({
|
||||||
|
slug: 'quickFilters',
|
||||||
|
filters: quickFilters,
|
||||||
|
checker: (dw: Dossier) => quickFilters.reduce((acc, f) => acc || (f.checked && f.checker(dw)), false)
|
||||||
|
});
|
||||||
|
|
||||||
|
const dossierFilters = entities.map(
|
||||||
|
dossier =>
|
||||||
|
new NestedFilter({
|
||||||
|
id: dossier.dossierName,
|
||||||
|
label: dossier.dossierName
|
||||||
|
})
|
||||||
|
);
|
||||||
|
filterGroups.push({
|
||||||
|
slug: 'dossierNameFilter',
|
||||||
|
label: this._translateService.instant('dossier-listing.filters.label'),
|
||||||
|
icon: 'red:folder',
|
||||||
|
filters: dossierFilters,
|
||||||
|
filterceptionPlaceholder: this._translateService.instant('dossier-listing.filters.search'),
|
||||||
|
checker: keyChecker('dossierName')
|
||||||
|
});
|
||||||
|
|
||||||
|
return filterGroups;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,34 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { IqserIconsModule } from '@iqser/common-ui';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { DossiersListingScreenComponent } from './screen/dossiers-listing-screen.component';
|
||||||
|
import { RouterModule } from '@angular/router';
|
||||||
|
import { DossiersListingActionsComponent } from './components/dossiers-listing-actions/dossiers-listing-actions.component';
|
||||||
|
import { SharedModule } from '@shared/shared.module';
|
||||||
|
import { DossiersListingDetailsComponent } from './components/dossiers-listing-details/dossiers-listing-details.component';
|
||||||
|
import { DossiersListingDossierNameComponent } from './components/dossiers-listing-dossier-name/dossiers-listing-dossier-name.component';
|
||||||
|
import { ConfigService } from './config.service';
|
||||||
|
import { TableItemComponent } from './components/table-item/table-item.component';
|
||||||
|
import { SharedDossiersModule } from '../../shared/shared-dossiers.module';
|
||||||
|
|
||||||
|
const routes = [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
component: DossiersListingScreenComponent,
|
||||||
|
pathMatch: 'full'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
DossiersListingScreenComponent,
|
||||||
|
DossiersListingActionsComponent,
|
||||||
|
DossiersListingDetailsComponent,
|
||||||
|
DossiersListingDossierNameComponent,
|
||||||
|
TableItemComponent
|
||||||
|
],
|
||||||
|
providers: [ConfigService],
|
||||||
|
imports: [RouterModule.forChild(routes), CommonModule, SharedModule, SharedDossiersModule, IqserIconsModule, TranslateModule]
|
||||||
|
})
|
||||||
|
export class DossiersListingModule {}
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
<section>
|
||||||
|
<redaction-page-header [buttonConfigs]="buttonConfigs"></redaction-page-header>
|
||||||
|
|
||||||
|
<div class="overlay-shadow"></div>
|
||||||
|
|
||||||
|
<div class="red-content-inner">
|
||||||
|
<div class="content-container">
|
||||||
|
<iqser-table
|
||||||
|
(noDataAction)="openAddDossierDialog()"
|
||||||
|
[hasScrollButton]="true"
|
||||||
|
[itemSize]="85"
|
||||||
|
[noDataButtonLabel]="'dossier-listing.no-data.action' | translate"
|
||||||
|
[noDataText]="'dossier-listing.no-data.title' | translate"
|
||||||
|
[noMatchText]="'dossier-listing.no-match.title' | translate"
|
||||||
|
[showNoDataButton]="currentUser.isManager"
|
||||||
|
noDataIcon="red:folder"
|
||||||
|
></iqser-table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="right-container" iqserHasScrollbar>
|
||||||
|
<redaction-dossiers-listing-details
|
||||||
|
*ngIf="(entitiesService.noData$ | async) === false"
|
||||||
|
[documentsChartData]="documentsChartData"
|
||||||
|
[dossiersChartData]="dossiersChartData"
|
||||||
|
></redaction-dossiers-listing-details>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<ng-template #needsWorkFilterTemplate let-filter="filter">
|
||||||
|
<redaction-type-filter [filter]="filter"></redaction-type-filter>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<ng-template #tableItemTemplate let-dossier="entity">
|
||||||
|
<redaction-table-item (calculateData)="calculateData()" [dossier]="dossier"></redaction-table-item>
|
||||||
|
</ng-template>
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
.right-container {
|
||||||
|
display: flex;
|
||||||
|
width: 466px;
|
||||||
|
min-width: 466px;
|
||||||
|
padding-right: 11px;
|
||||||
|
|
||||||
|
&.has-scrollbar:hover {
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
redaction-dossiers-listing-details {
|
||||||
|
min-width: 466px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,141 @@
|
|||||||
|
import { AfterViewInit, Component, forwardRef, Injector, OnDestroy, OnInit, TemplateRef, ViewChild } from '@angular/core';
|
||||||
|
import { DossierStatuses } from '@redaction/red-ui-http';
|
||||||
|
import { AppStateService } from '@state/app-state.service';
|
||||||
|
import { Dossier } from '@state/model/dossier';
|
||||||
|
import { UserService } from '@services/user.service';
|
||||||
|
import { PermissionsService } from '@services/permissions.service';
|
||||||
|
import { TranslateChartService } from '@services/translate-chart.service';
|
||||||
|
import { DoughnutChartConfig } from '@shared/components/simple-doughnut-chart/simple-doughnut-chart.component';
|
||||||
|
import { timer } from 'rxjs';
|
||||||
|
import { tap } from 'rxjs/operators';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { DossiersDialogService } from '../../../services/dossiers-dialog.service';
|
||||||
|
import { groupBy, OnAttach, OnDetach, StatusSorter } from '@utils/index';
|
||||||
|
import { DefaultListingServices, ListingComponent, TableComponent } from '@iqser/common-ui';
|
||||||
|
import { fileStatusTranslations } from '../../../translations/file-status-translations';
|
||||||
|
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||||
|
import { ConfigService } from '../config.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
templateUrl: './dossiers-listing-screen.component.html',
|
||||||
|
styleUrls: ['./dossiers-listing-screen.component.scss'],
|
||||||
|
providers: [...DefaultListingServices, { provide: ListingComponent, useExisting: forwardRef(() => DossiersListingScreenComponent) }]
|
||||||
|
})
|
||||||
|
export class DossiersListingScreenComponent
|
||||||
|
extends ListingComponent<Dossier>
|
||||||
|
implements OnInit, AfterViewInit, OnDestroy, OnAttach, OnDetach
|
||||||
|
{
|
||||||
|
readonly currentUser = this._userService.currentUser;
|
||||||
|
readonly tableColumnConfigs = this._configService.tableConfig;
|
||||||
|
readonly tableHeaderLabel = _('dossier-listing.table-header.title');
|
||||||
|
readonly buttonConfigs = this._configService.buttonsConfig(() => this.openAddDossierDialog());
|
||||||
|
dossiersChartData: DoughnutChartConfig[] = [];
|
||||||
|
documentsChartData: DoughnutChartConfig[] = [];
|
||||||
|
private _lastScrolledIndex: number;
|
||||||
|
@ViewChild('needsWorkFilterTemplate', {
|
||||||
|
read: TemplateRef,
|
||||||
|
static: true
|
||||||
|
})
|
||||||
|
private readonly _needsWorkFilterTemplate: TemplateRef<unknown>;
|
||||||
|
@ViewChild(TableComponent) private readonly _tableComponent: TableComponent<Dossier>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly _router: Router,
|
||||||
|
protected readonly _injector: Injector,
|
||||||
|
private readonly _userService: UserService,
|
||||||
|
readonly permissionsService: PermissionsService,
|
||||||
|
private readonly _appStateService: AppStateService,
|
||||||
|
private readonly _dialogService: DossiersDialogService,
|
||||||
|
private readonly _translateChartService: TranslateChartService,
|
||||||
|
private readonly _configService: ConfigService
|
||||||
|
) {
|
||||||
|
super(_injector);
|
||||||
|
this._appStateService.reset();
|
||||||
|
this._loadEntitiesFromState();
|
||||||
|
}
|
||||||
|
|
||||||
|
private get _activeDossiersCount(): number {
|
||||||
|
return this.entitiesService.all.filter(p => p.status === DossierStatuses.ACTIVE).length;
|
||||||
|
}
|
||||||
|
|
||||||
|
private get _inactiveDossiersCount(): number {
|
||||||
|
return this.entitiesService.all.length - this._activeDossiersCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.calculateData();
|
||||||
|
|
||||||
|
this.addSubscription = timer(0, 10000).subscribe(async () => {
|
||||||
|
await this._appStateService.loadAllDossiers();
|
||||||
|
this._loadEntitiesFromState();
|
||||||
|
this.calculateData();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.addSubscription = this._appStateService.fileChanged$.subscribe(() => {
|
||||||
|
this.calculateData();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngAfterViewInit(): void {
|
||||||
|
this.addSubscription = this._tableComponent.scrollViewport.scrolledIndexChange
|
||||||
|
.pipe(tap(index => (this._lastScrolledIndex = index)))
|
||||||
|
.subscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnAttach(): void {
|
||||||
|
this._appStateService.reset();
|
||||||
|
this._loadEntitiesFromState();
|
||||||
|
this.ngOnInit();
|
||||||
|
this.ngAfterViewInit();
|
||||||
|
this._tableComponent.scrollViewport.scrollToIndex(this._lastScrolledIndex, 'smooth');
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDetach(): void {
|
||||||
|
this.ngOnDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
openAddDossierDialog(): void {
|
||||||
|
this._dialogService.openDialog('addDossier', null, null, async addResponse => {
|
||||||
|
await this._router.navigate([`/main/dossiers/${addResponse.dossier.id}`]);
|
||||||
|
if (addResponse.addMembers) {
|
||||||
|
this._dialogService.openDialog('editDossier', null, {
|
||||||
|
dossier: addResponse.dossier,
|
||||||
|
section: 'members'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
calculateData(): void {
|
||||||
|
this._computeAllFilters();
|
||||||
|
|
||||||
|
this.dossiersChartData = [
|
||||||
|
{ value: this._activeDossiersCount, color: 'ACTIVE', label: _('active') },
|
||||||
|
{ value: this._inactiveDossiersCount, color: 'DELETED', label: _('archived') }
|
||||||
|
];
|
||||||
|
const groups = groupBy(this._appStateService.aggregatedFiles, 'status');
|
||||||
|
this.documentsChartData = [];
|
||||||
|
|
||||||
|
for (const status of Object.keys(groups)) {
|
||||||
|
this.documentsChartData.push({
|
||||||
|
value: groups[status].length,
|
||||||
|
color: status,
|
||||||
|
label: fileStatusTranslations[status],
|
||||||
|
key: status
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.documentsChartData.sort(StatusSorter.byStatus);
|
||||||
|
this.documentsChartData = this._translateChartService.translateStatus(this.documentsChartData);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _loadEntitiesFromState() {
|
||||||
|
this.entitiesService.setEntities(this._appStateService.allDossiers);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _computeAllFilters() {
|
||||||
|
const filterGroups = this._configService.filterGroups(this.entitiesService.all, this._needsWorkFilterTemplate);
|
||||||
|
for (const filterGroup of filterGroups) {
|
||||||
|
this.filterService.addFilterGroup(filterGroup);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -18,7 +18,7 @@ import { ManualRedactionEntryWrapper } from '@models/file/manual-redaction-entry
|
|||||||
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
|
||||||
import { ManualAnnotationResponse } from '@models/file/manual-annotation-response';
|
import { ManualAnnotationResponse } from '@models/file/manual-annotation-response';
|
||||||
import { AnnotationData, FileDataModel } from '@models/file/file-data.model';
|
import { AnnotationData, FileDataModel } from '@models/file/file-data.model';
|
||||||
import { FileActionService } from '../../services/file-action.service';
|
import { FileActionService } from '../../shared/services/file-action.service';
|
||||||
import { AnnotationDrawService } from '../../services/annotation-draw.service';
|
import { AnnotationDrawService } from '../../services/annotation-draw.service';
|
||||||
import { AnnotationProcessingService } from '../../services/annotation-processing.service';
|
import { AnnotationProcessingService } from '../../services/annotation-processing.service';
|
||||||
import { File } from '@models/file/file';
|
import { File } from '@models/file/file';
|
||||||
@ -38,7 +38,7 @@ import { TranslateService } from '@ngx-translate/core';
|
|||||||
import { fileStatusTranslations } from '../../translations/file-status-translations';
|
import { fileStatusTranslations } from '../../translations/file-status-translations';
|
||||||
import { handleFilterDelta } from '@utils/filter-utils';
|
import { handleFilterDelta } from '@utils/filter-utils';
|
||||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||||
import { FileActionsComponent } from '../../components/file-actions/file-actions.component';
|
import { FileActionsComponent } from '../../shared/components/file-actions/file-actions.component';
|
||||||
import { User } from '@models/user';
|
import { User } from '@models/user';
|
||||||
import { FilesService } from '../../services/files.service';
|
import { FilesService } from '../../services/files.service';
|
||||||
import Annotation = Core.Annotations.Annotation;
|
import Annotation = Core.Annotations.Annotation;
|
||||||
|
|||||||
@ -21,66 +21,64 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<ng-template #filenameTemplate let-item="entity">
|
<ng-template #tableItemTemplate let-item="entity">
|
||||||
<div class="cell filename">
|
<div>
|
||||||
<div [matTooltip]="item.filename" class="table-item-title heading" matTooltipPosition="above">
|
<div class="cell filename">
|
||||||
<span
|
<div [matTooltip]="item.filename" class="table-item-title heading" matTooltipPosition="above">
|
||||||
*ngIf="item.highlights.filename; else defaultFilename"
|
<span
|
||||||
[innerHTML]="item.highlights.filename[0]"
|
*ngIf="item.highlights.filename; else defaultFilename"
|
||||||
class="highlights"
|
[innerHTML]="item.highlights.filename[0]"
|
||||||
></span>
|
class="highlights"
|
||||||
<ng-template #defaultFilename>{{ item.filename }}</ng-template>
|
></span>
|
||||||
</div>
|
<ng-template #defaultFilename>{{ item.filename }}</ng-template>
|
||||||
|
|
||||||
<ng-container *ngIf="item.highlights['sections.text'] as highlights">
|
|
||||||
<div *ngIf="highlights.length > 0" class="small-label">
|
|
||||||
<span [innerHTML]="highlights[0]" class="highlights"></span>
|
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="highlights.length > 1" class="small-label">
|
|
||||||
<span [innerHTML]="highlights[1]" class="highlights"></span>
|
<ng-container *ngIf="item.highlights['sections.text'] as highlights">
|
||||||
|
<div *ngIf="highlights.length > 0" class="small-label">
|
||||||
|
<span [innerHTML]="highlights[0]" class="highlights"></span>
|
||||||
|
</div>
|
||||||
|
<div *ngIf="highlights.length > 1" class="small-label">
|
||||||
|
<span [innerHTML]="highlights[1]" class="highlights"></span>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<div *ngIf="item.unmatched?.length && item.unmatched as unmatched" class="small-label">
|
||||||
|
<span>
|
||||||
|
{{ 'search-screen.missing' | translate }}:<span *ngFor="let term of unmatched"
|
||||||
|
> <s>{{ term }}</s></span
|
||||||
|
>. {{ 'search-screen.must-contain' | translate }}:
|
||||||
|
<span
|
||||||
|
(click)="$event.stopPropagation(); updateNavigation(search$.getValue().query, term)"
|
||||||
|
*ngFor="let term of unmatched"
|
||||||
|
> <u>{{ term }}</u></span
|
||||||
|
>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</ng-container>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="item.unmatched?.length && item.unmatched as unmatched" class="small-label">
|
<div class="cell">
|
||||||
<span>
|
<iqser-status-bar
|
||||||
{{ 'search-screen.missing' | translate }}:<span *ngFor="let term of unmatched"
|
[configs]="[
|
||||||
> <s>{{ term }}</s></span
|
{
|
||||||
>. {{ 'search-screen.must-contain' | translate }}:
|
color: item.status,
|
||||||
<span (click)="$event.stopPropagation(); updateNavigation(search$.getValue().query, term)" *ngFor="let term of unmatched"
|
label: fileStatusTranslations[item.status] | translate,
|
||||||
> <u>{{ term }}</u></span
|
length: 1,
|
||||||
>
|
cssClass: 'all-caps-label'
|
||||||
</span>
|
}
|
||||||
</div>
|
]"
|
||||||
</div>
|
[small]="true"
|
||||||
</ng-template>
|
></iqser-status-bar>
|
||||||
|
</div>
|
||||||
<ng-template #statusTemplate let-item="entity">
|
|
||||||
<div class="cell">
|
<div class="cell small-label">
|
||||||
<iqser-status-bar
|
{{ item.dossierName }}
|
||||||
[configs]="[
|
</div>
|
||||||
{
|
|
||||||
color: item.status,
|
<div class="cell small-label stats-subtitle">
|
||||||
label: fileStatusTranslations[item.status] | translate,
|
<div>
|
||||||
length: 1,
|
<mat-icon svgIcon="red:pages"></mat-icon>
|
||||||
cssClass: 'all-caps-label'
|
{{ item.numberOfPages }}
|
||||||
}
|
</div>
|
||||||
]"
|
|
||||||
[small]="true"
|
|
||||||
></iqser-status-bar>
|
|
||||||
</div>
|
|
||||||
</ng-template>
|
|
||||||
|
|
||||||
<ng-template #dossierTemplate let-item="entity">
|
|
||||||
<div class="cell small-label">
|
|
||||||
{{ item.dossierName }}
|
|
||||||
</div>
|
|
||||||
</ng-template>
|
|
||||||
|
|
||||||
<ng-template #pagesTemplate let-item="entity">
|
|
||||||
<div class="cell small-label stats-subtitle">
|
|
||||||
<div>
|
|
||||||
<mat-icon svgIcon="red:pages"></mat-icon>
|
|
||||||
{{ item.numberOfPages }}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|||||||
@ -1,11 +1,9 @@
|
|||||||
@use 'common-mixins';
|
@use 'common-mixins';
|
||||||
|
|
||||||
:host ::ng-deep iqser-table cdk-virtual-scroll-viewport .cdk-virtual-scroll-content-wrapper .table-item > div.cell {
|
.cell .highlights::ng-deep {
|
||||||
.highlights {
|
@include common-mixins.line-clamp(1);
|
||||||
@include common-mixins.line-clamp(1);
|
|
||||||
|
|
||||||
em {
|
em {
|
||||||
background-color: #fffcc4;
|
background-color: #fffcc4;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { Component, forwardRef, Injector, OnDestroy, OnInit, TemplateRef, ViewChild } from '@angular/core';
|
import { Component, forwardRef, Injector, OnDestroy } from '@angular/core';
|
||||||
import {
|
import {
|
||||||
DefaultListingServices,
|
DefaultListingServices,
|
||||||
IListable,
|
IListable,
|
||||||
@ -40,17 +40,17 @@ interface SearchInput {
|
|||||||
styleUrls: ['./search-screen.component.scss'],
|
styleUrls: ['./search-screen.component.scss'],
|
||||||
providers: [...DefaultListingServices, { provide: ListingComponent, useExisting: forwardRef(() => SearchScreenComponent) }]
|
providers: [...DefaultListingServices, { provide: ListingComponent, useExisting: forwardRef(() => SearchScreenComponent) }]
|
||||||
})
|
})
|
||||||
export class SearchScreenComponent extends ListingComponent<ListItem> implements OnDestroy, OnInit {
|
export class SearchScreenComponent extends ListingComponent<ListItem> implements OnDestroy {
|
||||||
readonly fileStatusTranslations = fileStatusTranslations;
|
readonly fileStatusTranslations = fileStatusTranslations;
|
||||||
readonly searchPositions = SearchPositions;
|
readonly searchPositions = SearchPositions;
|
||||||
|
|
||||||
@ViewChild('filenameTemplate', { static: true }) readonly filenameTemplate: TemplateRef<unknown>;
|
|
||||||
@ViewChild('statusTemplate', { static: true }) readonly statusTemplate: TemplateRef<unknown>;
|
|
||||||
@ViewChild('dossierTemplate', { static: true }) readonly dossierTemplate: TemplateRef<unknown>;
|
|
||||||
@ViewChild('pagesTemplate', { static: true }) readonly pagesTemplate: TemplateRef<unknown>;
|
|
||||||
|
|
||||||
readonly tableHeaderLabel = _('search-screen.table-header');
|
readonly tableHeaderLabel = _('search-screen.table-header');
|
||||||
tableColumnConfigs: TableColumnConfig<ListItem>[];
|
readonly tableColumnConfigs: TableColumnConfig<ListItem>[] = [
|
||||||
|
{ label: _('search-screen.cols.document'), width: '2fr' },
|
||||||
|
{ label: _('search-screen.cols.status') },
|
||||||
|
{ label: _('search-screen.cols.dossier') },
|
||||||
|
{ label: _('search-screen.cols.pages'), width: 'auto' }
|
||||||
|
];
|
||||||
readonly search$ = new BehaviorSubject<SearchInput>(null);
|
readonly search$ = new BehaviorSubject<SearchInput>(null);
|
||||||
readonly searchResults$: Observable<ListItem[]> = this.search$.asObservable().pipe(
|
readonly searchResults$: Observable<ListItem[]> = this.search$.asObservable().pipe(
|
||||||
switchMap(query => this._search(query)),
|
switchMap(query => this._search(query)),
|
||||||
@ -108,19 +108,6 @@ export class SearchScreenComponent extends ListingComponent<ListItem> implements
|
|||||||
this._router.navigate([], { queryParams }).then();
|
this._router.navigate([], { queryParams }).then();
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
|
||||||
this._configureTableColumns();
|
|
||||||
}
|
|
||||||
|
|
||||||
private _configureTableColumns() {
|
|
||||||
this.tableColumnConfigs = [
|
|
||||||
{ label: _('search-screen.cols.document'), template: this.filenameTemplate, width: '2fr' },
|
|
||||||
{ label: _('search-screen.cols.status'), template: this.statusTemplate },
|
|
||||||
{ label: _('search-screen.cols.dossier'), template: this.dossierTemplate },
|
|
||||||
{ label: _('search-screen.cols.pages'), template: this.pagesTemplate, width: 'auto' }
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
private _search(searchInput: SearchInput): Observable<SearchResult> {
|
private _search(searchInput: SearchInput): Observable<SearchResult> {
|
||||||
return this._searchControllerService.search({
|
return this._searchControllerService.search({
|
||||||
dossierIds: [...searchInput.dossierIds],
|
dossierIds: [...searchInput.dossierIds],
|
||||||
|
|||||||
@ -2,8 +2,7 @@ import { Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, S
|
|||||||
import { PermissionsService } from '@services/permissions.service';
|
import { PermissionsService } from '@services/permissions.service';
|
||||||
import { File } from '@models/file/file';
|
import { File } from '@models/file/file';
|
||||||
import { AppStateService } from '@state/app-state.service';
|
import { AppStateService } from '@state/app-state.service';
|
||||||
import { FileActionService } from '../../services/file-action.service';
|
import { DossiersDialogService } from '../../../services/dossiers-dialog.service';
|
||||||
import { DossiersDialogService } from '../../services/dossiers-dialog.service';
|
|
||||||
import { ConfirmationDialogInput } from '@shared/dialogs/confirmation-dialog/confirmation-dialog.component';
|
import { ConfirmationDialogInput } from '@shared/dialogs/confirmation-dialog/confirmation-dialog.component';
|
||||||
import { AutoUnsubscribe, CircleButtonType, CircleButtonTypes, LoadingService, Required, StatusBarConfig, Toaster } from '@iqser/common-ui';
|
import { AutoUnsubscribe, CircleButtonType, CircleButtonTypes, LoadingService, Required, StatusBarConfig, Toaster } from '@iqser/common-ui';
|
||||||
import { FileManagementControllerService, FileStatus } from '@redaction/red-ui-http';
|
import { FileManagementControllerService, FileStatus } from '@redaction/red-ui-http';
|
||||||
@ -12,6 +11,7 @@ import { UserService } from '@services/user.service';
|
|||||||
import { filter } from 'rxjs/operators';
|
import { filter } from 'rxjs/operators';
|
||||||
import { UserPreferenceService } from '@services/user-preference.service';
|
import { UserPreferenceService } from '@services/user-preference.service';
|
||||||
import { LongPressEvent } from '@shared/directives/long-press.directive';
|
import { LongPressEvent } from '@shared/directives/long-press.directive';
|
||||||
|
import { FileActionService } from '../../services/file-action.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'redaction-file-actions',
|
selector: 'redaction-file-actions',
|
||||||
@ -1,7 +1,7 @@
|
|||||||
import { Component, Input } from '@angular/core';
|
import { Component, Input } from '@angular/core';
|
||||||
import { AppStateService } from '@state/app-state.service';
|
import { AppStateService } from '@state/app-state.service';
|
||||||
import { File } from '@models/file/file';
|
import { File } from '@models/file/file';
|
||||||
import { Dossier } from '../../../../state/model/dossier';
|
import { Dossier } from '@state/model/dossier';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'redaction-needs-work-badge',
|
selector: 'redaction-needs-work-badge',
|
||||||
@ -4,10 +4,10 @@ import { UserService } from '@services/user.service';
|
|||||||
import { ReanalysisControllerService } from '@redaction/red-ui-http';
|
import { ReanalysisControllerService } from '@redaction/red-ui-http';
|
||||||
import { File } from '@models/file/file';
|
import { File } from '@models/file/file';
|
||||||
import { PermissionsService } from '@services/permissions.service';
|
import { PermissionsService } from '@services/permissions.service';
|
||||||
import { DossiersDialogService } from './dossiers-dialog.service';
|
import { DossiersDialogService } from '../../services/dossiers-dialog.service';
|
||||||
import { ConfirmationDialogInput } from '@shared/dialogs/confirmation-dialog/confirmation-dialog.component';
|
import { ConfirmationDialogInput } from '@shared/dialogs/confirmation-dialog/confirmation-dialog.component';
|
||||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||||
import { FilesService } from './files.service';
|
import { FilesService } from '../../services/files.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class FileActionService {
|
export class FileActionService {
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { FileActionService } from './services/file-action.service';
|
||||||
|
import { FileActionsComponent } from './components/file-actions/file-actions.component';
|
||||||
|
import { NeedsWorkBadgeComponent } from './components/needs-work-badge/needs-work-badge.component';
|
||||||
|
import { IqserIconsModule } from '@iqser/common-ui';
|
||||||
|
import { SharedModule } from '@shared/shared.module';
|
||||||
|
|
||||||
|
const components = [FileActionsComponent, NeedsWorkBadgeComponent];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [...components],
|
||||||
|
exports: [...components],
|
||||||
|
providers: [FileActionService],
|
||||||
|
imports: [CommonModule, IqserIconsModule, SharedModule]
|
||||||
|
})
|
||||||
|
export class SharedDossiersModule {}
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { Component, Input, OnInit } from '@angular/core';
|
import { Component, Input, OnInit } from '@angular/core';
|
||||||
import { AppStateService } from '@state/app-state.service';
|
import { AppStateService } from '../../../../state/app-state.service';
|
||||||
import { INestedFilter } from '@iqser/common-ui';
|
import { INestedFilter } from '@iqser/common-ui';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -19,13 +19,15 @@ import { MomentDateAdapter } from '@angular/material-moment-adapter';
|
|||||||
import { SelectComponent } from './components/select/select.component';
|
import { SelectComponent } from './components/select/select.component';
|
||||||
import { NavigateLastDossiersScreenDirective } from './directives/navigate-last-dossiers-screen.directive';
|
import { NavigateLastDossiersScreenDirective } from './directives/navigate-last-dossiers-screen.directive';
|
||||||
import { DictionaryManagerComponent } from './components/dictionary-manager/dictionary-manager.component';
|
import { DictionaryManagerComponent } from './components/dictionary-manager/dictionary-manager.component';
|
||||||
import { SideNavComponent } from '@shared/components/side-nav/side-nav.component';
|
import { SideNavComponent } from './components/side-nav/side-nav.component';
|
||||||
import { MonacoEditorModule } from '@materia-ui/ngx-monaco-editor';
|
import { MonacoEditorModule } from '@materia-ui/ngx-monaco-editor';
|
||||||
import { AssignUserDropdownComponent } from './components/assign-user-dropdown/assign-user-dropdown.component';
|
import { AssignUserDropdownComponent } from './components/assign-user-dropdown/assign-user-dropdown.component';
|
||||||
import { PageHeaderComponent } from './components/page-header/page-header.component';
|
import { PageHeaderComponent } from './components/page-header/page-header.component';
|
||||||
import { DatePipe } from '@shared/pipes/date.pipe';
|
import { DatePipe } from './pipes/date.pipe';
|
||||||
import { LongPressDirective } from '@shared/directives/long-press.directive';
|
import { LongPressDirective } from './directives/long-press.directive';
|
||||||
import { NamePipe } from '@shared/pipes/name.pipe';
|
import { NamePipe } from './pipes/name.pipe';
|
||||||
|
import { TypeFilterComponent } from './components/type-filter/type-filter.component';
|
||||||
|
import { TeamMembersComponent } from './components/team-members/team-members.component';
|
||||||
|
|
||||||
const buttons = [FileDownloadBtnComponent, UserButtonComponent];
|
const buttons = [FileDownloadBtnComponent, UserButtonComponent];
|
||||||
|
|
||||||
@ -42,6 +44,8 @@ const components = [
|
|||||||
DictionaryManagerComponent,
|
DictionaryManagerComponent,
|
||||||
AssignUserDropdownComponent,
|
AssignUserDropdownComponent,
|
||||||
PageHeaderComponent,
|
PageHeaderComponent,
|
||||||
|
TypeFilterComponent,
|
||||||
|
TeamMembersComponent,
|
||||||
|
|
||||||
...buttons
|
...buttons
|
||||||
];
|
];
|
||||||
|
|||||||
17
apps/red-ui/src/app/utils/index.ts
Normal file
17
apps/red-ui/src/app/utils/index.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
export * from './sorters/redaction-filter-sorter';
|
||||||
|
export * from './sorters/status-sorter';
|
||||||
|
export * from './sorters/super-type-sorter';
|
||||||
|
|
||||||
|
export * from './api-path-interceptor';
|
||||||
|
export * from './configuration.initializer';
|
||||||
|
export * from './custom-route-reuse.strategy';
|
||||||
|
export * from './date-inputs-utils';
|
||||||
|
export * from './file-download-utils';
|
||||||
|
export * from './file-drop-utils';
|
||||||
|
export * from './filter-utils';
|
||||||
|
export * from './functions';
|
||||||
|
export * from './global-error-handler.service';
|
||||||
|
export * from './missing-translations-handler';
|
||||||
|
export * from './page-stamper';
|
||||||
|
export * from './pdf-coordinates';
|
||||||
|
export * from './pruning-translation-loader';
|
||||||
@ -1375,7 +1375,7 @@
|
|||||||
"table-header": "{length} search {length, plural, one{result} other{results}}"
|
"table-header": "{length} search {length, plural, one{result} other{results}}"
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
"entire-platform": "accross all dossiers",
|
"entire-platform": "across all dossiers",
|
||||||
"placeholder": "Search documents...",
|
"placeholder": "Search documents...",
|
||||||
"this-dossier": "in this dossier"
|
"this-dossier": "in this dossier"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
Subproject commit 54eb8173cf7ce35f29f4e9df309954bbd1cf7a5c
|
Subproject commit c0b445b06ed86a07c4b9aa1803b29b9384021d54
|
||||||
Loading…
x
Reference in New Issue
Block a user