Pull request #284: Table component
Merge in RED/ui from table-component to master * commit '340b06c7ba5edc767c47d326a0cff11f0512d9cb': Some fixes & improvements Fixed merge Cleanup Audit screen Search screen Documents trash Downloads listing Trash screen Active fields listing Dossier attributes listing File attributes listing Default colors User listing Dossier overview Dossier listing Moved empty states, templates listing Table component WIP - dictionary listing
This commit is contained in:
commit
21d3e8e05a
@ -6,70 +6,15 @@
|
||||
|
||||
<div class="red-content-inner">
|
||||
<div class="content-container">
|
||||
<iqser-table-header
|
||||
<iqser-table
|
||||
[actionsTemplate]="actionsTemplate"
|
||||
[bulkActions]="bulkActions"
|
||||
[hasEmptyColumn]="true"
|
||||
[itemSize]="80"
|
||||
[noDataText]="'downloads-list.no-data.title' | translate"
|
||||
[selectionEnabled]="true"
|
||||
[tableColumnConfigs]="tableColumnConfigs"
|
||||
[tableHeaderLabel]="tableHeaderLabel"
|
||||
></iqser-table-header>
|
||||
|
||||
<redaction-empty-state
|
||||
*ngIf="entitiesService.noData$ | async"
|
||||
[text]="'downloads-list.no-data.title' | translate"
|
||||
icon="red:download"
|
||||
></redaction-empty-state>
|
||||
|
||||
<cdk-virtual-scroll-viewport [itemSize]="itemSize" redactionHasScrollbar>
|
||||
<!-- Table lines -->
|
||||
<div *cdkVirtualFor="let download of sortedDisplayedEntities$ | async; trackBy: trackByPrimaryKey" class="table-item">
|
||||
<div (click)="toggleEntitySelected($event, download)" class="selection-column">
|
||||
<iqser-round-checkbox [active]="isSelected(download)"></iqser-round-checkbox>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div [class.no-bold]="download.lastDownload" class="table-item-title heading">
|
||||
{{ download.filename }}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="small-label">
|
||||
{{ download.size }}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="small-label">
|
||||
{{ download.creationDate | date: 'd MMM. yyyy, hh:mm a' }}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="small-label">
|
||||
{{ download.status }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="actions-container">
|
||||
<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"
|
||||
icon="red:download"
|
||||
[type]="circleButtonTypes.dark"
|
||||
></iqser-circle-button>
|
||||
|
||||
<iqser-circle-button
|
||||
(action)="deleteItems([download])"
|
||||
[tooltip]="'downloads-list.actions.delete' | translate"
|
||||
icon="red:trash"
|
||||
[type]="circleButtonTypes.dark"
|
||||
></iqser-circle-button>
|
||||
|
||||
<mat-spinner *ngIf="download.inProgress" diameter="15"></mat-spinner>
|
||||
</div>
|
||||
</div>
|
||||
<div class="scrollbar-placeholder"></div>
|
||||
</div>
|
||||
</cdk-virtual-scroll-viewport>
|
||||
emptyColumnWidth="auto"
|
||||
noDataIcon="red:download"
|
||||
></iqser-table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@ -79,7 +24,60 @@
|
||||
(action)="deleteItems()"
|
||||
*ngIf="entitiesService.areSomeSelected$ | async"
|
||||
[tooltip]="'downloads-list.bulk.delete' | translate"
|
||||
icon="red:trash"
|
||||
[type]="circleButtonTypes.dark"
|
||||
icon="red:trash"
|
||||
></iqser-circle-button>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #filenameTemplate let-download="entity">
|
||||
<div class="cell">
|
||||
<div [class.no-bold]="download.lastDownload" class="table-item-title heading">
|
||||
{{ download.filename }}
|
||||
</div>
|
||||
</div>
|
||||
</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,27 +1,3 @@
|
||||
.content-container {
|
||||
cdk-virtual-scroll-viewport {
|
||||
::ng-deep.cdk-virtual-scroll-content-wrapper {
|
||||
grid-template-columns: auto 2fr 1fr 1fr 1fr auto 11px;
|
||||
|
||||
.table-item {
|
||||
> div:not(.scrollbar-placeholder) {
|
||||
padding-left: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.has-scrollbar:hover {
|
||||
::ng-deep.cdk-virtual-scroll-content-wrapper {
|
||||
grid-template-columns: auto 2fr 1fr 1fr 1fr auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.page-header .actions {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.no-bold {
|
||||
font-weight: normal !important;
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Component, Injector, OnInit } from '@angular/core';
|
||||
import { Component, forwardRef, Injector, OnInit, TemplateRef, ViewChild } from '@angular/core';
|
||||
import { FileDownloadService } from '@upload-download/services/file-download.service';
|
||||
import { DownloadStatusWrapper } from '@upload-download/model/download-status.wrapper';
|
||||
import { DownloadControllerService } from '@redaction/red-ui-http';
|
||||
@ -11,18 +11,16 @@ import { RouterHistoryService } from '@services/router-history.service';
|
||||
selector: 'redaction-downloads-list-screen',
|
||||
templateUrl: './downloads-list-screen.component.html',
|
||||
styleUrls: ['./downloads-list-screen.component.scss'],
|
||||
providers: [...DefaultListingServices]
|
||||
providers: [...DefaultListingServices, { provide: ListingComponent, useExisting: forwardRef(() => DownloadsListScreenComponent) }]
|
||||
})
|
||||
export class DownloadsListScreenComponent extends ListingComponent<DownloadStatusWrapper> implements OnInit {
|
||||
readonly itemSize = 80;
|
||||
readonly circleButtonTypes = CircleButtonTypes;
|
||||
readonly tableHeaderLabel = _('downloads-list.table-header.title');
|
||||
readonly tableColumnConfigs: readonly TableColumnConfig<DownloadStatusWrapper>[] = [
|
||||
{ label: _('downloads-list.table-col-names.name') },
|
||||
{ label: _('downloads-list.table-col-names.size') },
|
||||
{ label: _('downloads-list.table-col-names.date') },
|
||||
{ label: _('downloads-list.table-col-names.status') }
|
||||
];
|
||||
tableColumnConfigs: TableColumnConfig<DownloadStatusWrapper>[];
|
||||
@ViewChild('filenameTemplate', { static: true }) filenameTemplate: TemplateRef<never>;
|
||||
@ViewChild('sizeTemplate', { static: true }) sizeTemplate: TemplateRef<never>;
|
||||
@ViewChild('creationDateTemplate', { static: true }) creationDateTemplate: TemplateRef<never>;
|
||||
@ViewChild('statusTemplate', { static: true }) statusTemplate: TemplateRef<never>;
|
||||
protected readonly _primaryKey = 'storageId';
|
||||
|
||||
constructor(
|
||||
@ -36,10 +34,13 @@ export class DownloadsListScreenComponent extends ListingComponent<DownloadStatu
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
this._loadingService.loadWhile(this._loadData());
|
||||
this._configureTableColumns();
|
||||
this._loadingService.start();
|
||||
await this._loadData();
|
||||
this.addSubscription = timer(0, 5000).subscribe(async () => {
|
||||
await this._loadData();
|
||||
});
|
||||
this._loadingService.stop();
|
||||
}
|
||||
|
||||
downloadItem(download: DownloadStatusWrapper) {
|
||||
@ -50,6 +51,15 @@ export class DownloadsListScreenComponent extends ListingComponent<DownloadStatu
|
||||
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[]) {
|
||||
const storageIds = (downloads || this.entitiesService.selected).map(d => d.storageId);
|
||||
await this._downloadControllerService.deleteDownload({ storageIds }).toPromise();
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
<iqser-circle-button [matMenuTriggerFor]="overlay" [showDot]="hasUnreadNotifications" icon="red:notification"></iqser-circle-button>
|
||||
<mat-menu #overlay="matMenu" backdropClass="notifications-backdrop" class="notifications-menu" xPosition="before">
|
||||
<redaction-empty-state
|
||||
<iqser-empty-state
|
||||
*ngIf="!groupedNotifications.length"
|
||||
[horizontalPadding]="40"
|
||||
[text]="'notifications.no-data' | translate"
|
||||
[verticalPadding]="0"
|
||||
></redaction-empty-state>
|
||||
></iqser-empty-state>
|
||||
|
||||
<div *ngFor="let group of groupedNotifications">
|
||||
<div class="all-caps-label">{{ group.dateString }}</div>
|
||||
|
||||
38
apps/red-ui/src/app/models/audit-model-wrapper.model.ts
Normal file
38
apps/red-ui/src/app/models/audit-model-wrapper.model.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import { AuditModel } from '@redaction/red-ui-http';
|
||||
import { Listable } from '@iqser/common-ui';
|
||||
|
||||
export class AuditModelWrapper implements Listable {
|
||||
constructor(public auditModel: AuditModel) {}
|
||||
|
||||
get category(): string {
|
||||
return this.auditModel.category;
|
||||
}
|
||||
|
||||
get details(): any {
|
||||
return this.auditModel.details;
|
||||
}
|
||||
|
||||
get message(): string {
|
||||
return this.auditModel.message;
|
||||
}
|
||||
|
||||
get recordId(): string {
|
||||
return this.auditModel.recordId;
|
||||
}
|
||||
|
||||
get recordDate(): string {
|
||||
return this.auditModel.recordDate;
|
||||
}
|
||||
|
||||
get objectId(): string {
|
||||
return this.auditModel.objectId;
|
||||
}
|
||||
|
||||
get userId(): string {
|
||||
return this.auditModel.userId;
|
||||
}
|
||||
|
||||
get id() {
|
||||
return this.auditModel.recordDate;
|
||||
}
|
||||
}
|
||||
@ -63,8 +63,8 @@
|
||||
<textarea
|
||||
[placeholder]="'add-edit-dictionary.form.description-placeholder' | translate"
|
||||
formControlName="description"
|
||||
iqserHasScrollbar
|
||||
name="description"
|
||||
redactionHasScrollbar
|
||||
rows="4"
|
||||
type="text"
|
||||
></textarea>
|
||||
|
||||
@ -1,74 +1,14 @@
|
||||
<iqser-table-header
|
||||
<iqser-table
|
||||
[actionsTemplate]="actionsTemplate"
|
||||
[bulkActions]="bulkActions"
|
||||
[hasEmptyColumn]="true"
|
||||
[itemMouseEnterFn]="itemMouseEnterFn"
|
||||
[itemMouseLeaveFn]="itemMouseLeaveFn"
|
||||
[itemSize]="50"
|
||||
[noDataText]="'file-attributes-csv-import.no-data.title' | translate"
|
||||
[selectionEnabled]="true"
|
||||
[tableColumnConfigs]="tableColumnConfigs"
|
||||
[tableHeaderLabel]="tableHeaderLabel"
|
||||
></iqser-table-header>
|
||||
|
||||
<redaction-empty-state
|
||||
*ngIf="entitiesService.noData$ | async"
|
||||
[text]="'file-attributes-csv-import.no-data.title' | translate"
|
||||
icon="red:attribute"
|
||||
></redaction-empty-state>
|
||||
|
||||
<cdk-virtual-scroll-viewport [itemSize]="50" redactionHasScrollbar>
|
||||
<div
|
||||
(mouseenter)="setHoveredColumn.emit(field.csvColumn)"
|
||||
(mouseleave)="setHoveredColumn.emit()"
|
||||
*cdkVirtualFor="let field of sortedDisplayedEntities$ | async; trackBy: trackByPrimaryKey"
|
||||
class="table-item"
|
||||
>
|
||||
<div (click)="toggleEntitySelected($event, field)" class="selection-column">
|
||||
<iqser-round-checkbox [active]="isSelected(field)"></iqser-round-checkbox>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<iqser-editable-input
|
||||
(save)="field.name = $event"
|
||||
[buttonsType]="circleButtonTypes.dark"
|
||||
[cancelTooltip]="'file-attributes-csv-import.action.cancel-edit-name' | translate"
|
||||
[class]="'w-200'"
|
||||
[editTooltip]="'file-attributes-csv-import.action.edit-name' | translate"
|
||||
[saveTooltip]="'file-attributes-csv-import.action.save-name' | translate"
|
||||
[value]="field.name"
|
||||
></iqser-editable-input>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="iqser-input-group">
|
||||
<mat-form-field class="no-label">
|
||||
<mat-select [(ngModel)]="field.type">
|
||||
<mat-option *ngFor="let type of typeOptions" [value]="type">
|
||||
{{ translations[type] | translate }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="center">
|
||||
<mat-slide-toggle [(ngModel)]="field.readonly" color="primary"></mat-slide-toggle>
|
||||
</div>
|
||||
|
||||
<div class="center">
|
||||
<iqser-round-checkbox (click)="togglePrimary(field)" [active]="field.primaryAttribute"></iqser-round-checkbox>
|
||||
</div>
|
||||
|
||||
<div class="actions-container">
|
||||
<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 class="scrollbar-placeholder"></div>
|
||||
</div>
|
||||
</cdk-virtual-scroll-viewport>
|
||||
emptyColumnWidth="auto"
|
||||
noDataIcon="red:attribute"
|
||||
></iqser-table>
|
||||
|
||||
<ng-template #bulkActions>
|
||||
<ng-container *ngIf="entitiesService.areSomeSelected$ | async">
|
||||
@ -113,3 +53,55 @@
|
||||
</mat-menu>
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #actionsTemplate let-field="entity">
|
||||
<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>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #labelTemplate let-field="entity">
|
||||
<div class="cell">
|
||||
<iqser-editable-input
|
||||
(save)="field.name = $event"
|
||||
[buttonsType]="circleButtonTypes.dark"
|
||||
[cancelTooltip]="'file-attributes-csv-import.action.cancel-edit-name' | translate"
|
||||
[class]="'w-200'"
|
||||
[editTooltip]="'file-attributes-csv-import.action.edit-name' | translate"
|
||||
[saveTooltip]="'file-attributes-csv-import.action.save-name' | translate"
|
||||
[value]="field.name"
|
||||
></iqser-editable-input>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #typeTemplate let-field="entity">
|
||||
<div class="cell">
|
||||
<div class="iqser-input-group">
|
||||
<mat-form-field class="no-label">
|
||||
<mat-select [(ngModel)]="field.type">
|
||||
<mat-option *ngFor="let type of typeOptions" [value]="type">
|
||||
{{ translations[type] | translate }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
</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>
|
||||
|
||||
@ -1,47 +1,37 @@
|
||||
@import '../../../../../../assets/styles/variables';
|
||||
|
||||
iqser-table-header::ng-deep iqser-table-column-name .name {
|
||||
padding-left: 22px;
|
||||
}
|
||||
:host ::ng-deep iqser-table {
|
||||
iqser-table-header {
|
||||
iqser-table-column-name .name {
|
||||
padding-left: 22px;
|
||||
}
|
||||
|
||||
iqser-table-header::ng-deep .header-item {
|
||||
box-shadow: none;
|
||||
border-top: 1px solid $separator;
|
||||
.header-item {
|
||||
box-shadow: none;
|
||||
border-top: 1px solid $separator;
|
||||
|
||||
.all-caps-label {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
iqser-circle-button {
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
.separator {
|
||||
margin-left: 14px;
|
||||
background-color: $separator;
|
||||
width: 1px;
|
||||
height: 30px;
|
||||
margin-right: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
cdk-virtual-scroll-viewport {
|
||||
height: calc(100% - 80px);
|
||||
|
||||
::ng-deep.cdk-virtual-scroll-content-wrapper {
|
||||
grid-template-columns: 30px minmax(0, 350px) 150px auto auto auto 11px;
|
||||
|
||||
.table-item > div {
|
||||
height: 50px;
|
||||
|
||||
&:not(.scrollbar-placeholder) {
|
||||
padding-left: 10px;
|
||||
|
||||
&.center {
|
||||
align-items: center;
|
||||
}
|
||||
.all-caps-label {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
iqser-circle-button {
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
.separator {
|
||||
margin-left: 14px;
|
||||
background-color: $separator;
|
||||
width: 1px;
|
||||
height: 30px;
|
||||
margin-right: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cdk-virtual-scroll-viewport {
|
||||
height: calc(100% - 80px) !important;
|
||||
|
||||
.cdk-virtual-scroll-content-wrapper .table-item > div.cell {
|
||||
iqser-editable-input:not(.editing) {
|
||||
padding-left: 12px;
|
||||
}
|
||||
@ -55,10 +45,4 @@ cdk-virtual-scroll-viewport {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.has-scrollbar:hover {
|
||||
::ng-deep.cdk-virtual-scroll-content-wrapper {
|
||||
grid-template-columns: 30px minmax(0, 350px) 150px auto auto auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,16 @@
|
||||
import { Component, EventEmitter, Injector, Input, OnChanges, Output, SimpleChanges } from '@angular/core';
|
||||
import {
|
||||
Component,
|
||||
EventEmitter,
|
||||
forwardRef,
|
||||
Injector,
|
||||
Input,
|
||||
OnChanges,
|
||||
OnInit,
|
||||
Output,
|
||||
SimpleChanges,
|
||||
TemplateRef,
|
||||
ViewChild
|
||||
} from '@angular/core';
|
||||
import { Field } from '../file-attributes-csv-import-dialog.component';
|
||||
import { FileAttributeConfig } from '@redaction/red-ui-http';
|
||||
import { CircleButtonTypes, DefaultListingServices, ListingComponent, TableColumnConfig } from '@iqser/common-ui';
|
||||
@ -9,46 +21,36 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
selector: 'redaction-active-fields-listing',
|
||||
templateUrl: './active-fields-listing.component.html',
|
||||
styleUrls: ['./active-fields-listing.component.scss'],
|
||||
providers: [...DefaultListingServices]
|
||||
providers: [...DefaultListingServices, { provide: ListingComponent, useExisting: forwardRef(() => ActiveFieldsListingComponent) }]
|
||||
})
|
||||
export class ActiveFieldsListingComponent extends ListingComponent<Field> implements OnChanges {
|
||||
protected readonly _primaryKey = 'csvColumn';
|
||||
export class ActiveFieldsListingComponent extends ListingComponent<Field> implements OnChanges, OnInit {
|
||||
readonly circleButtonTypes = CircleButtonTypes;
|
||||
readonly translations = fileAttributeTypesTranslations;
|
||||
readonly tableHeaderLabel = _('file-attributes-csv-import.table-header.title');
|
||||
readonly tableColumnConfigs: TableColumnConfig<Field>[] = [
|
||||
{
|
||||
label: _('file-attributes-csv-import.table-col-names.name'),
|
||||
class: 'name'
|
||||
},
|
||||
{ label: _('file-attributes-csv-import.table-col-names.type') },
|
||||
{
|
||||
label: _('file-attributes-csv-import.table-col-names.read-only'),
|
||||
class: 'flex-center',
|
||||
leftIcon: 'red:read-only'
|
||||
},
|
||||
{
|
||||
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')
|
||||
}
|
||||
];
|
||||
tableColumnConfigs: TableColumnConfig<Field>[];
|
||||
readonly typeOptions = [
|
||||
FileAttributeConfig.TypeEnum.TEXT,
|
||||
FileAttributeConfig.TypeEnum.NUMBER,
|
||||
FileAttributeConfig.TypeEnum.DATE
|
||||
] as const;
|
||||
|
||||
@ViewChild('labelTemplate', { static: true }) labelTemplate: TemplateRef<never>;
|
||||
@ViewChild('typeTemplate', { static: true }) typeTemplate: TemplateRef<never>;
|
||||
@ViewChild('readonlyTemplate', { static: true }) readonlyTemplate: TemplateRef<never>;
|
||||
@ViewChild('primaryTemplate', { static: true }) primaryTemplate: TemplateRef<never>;
|
||||
@Input() entities: Field[];
|
||||
@Output() entitiesChange = new EventEmitter<Field[]>();
|
||||
@Output() setHoveredColumn = new EventEmitter<string>();
|
||||
@Output() toggleFieldActive = new EventEmitter<Field>();
|
||||
protected readonly _primaryKey = 'csvColumn';
|
||||
|
||||
constructor(protected readonly _injector: Injector) {
|
||||
super(_injector);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this._configureTableColumns();
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
if (changes.entities) {
|
||||
this.entitiesService.setEntities(this.entities);
|
||||
@ -80,4 +82,38 @@ export class ActiveFieldsListingComponent extends ListingComponent<Field> implem
|
||||
}
|
||||
field.primaryAttribute = true;
|
||||
}
|
||||
|
||||
itemMouseEnterFn = (field: Field) => this.setHoveredColumn.emit(field.csvColumn);
|
||||
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'
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@ -34,6 +34,7 @@ export class FileAttributesCsvImportDialogComponent extends ListingComponent<Fie
|
||||
keepPreview = false;
|
||||
columnSample = [];
|
||||
initialParseConfig: { delimiter?: string; encoding?: string } = {};
|
||||
readonly tableHeaderLabel = '';
|
||||
protected readonly _primaryKey = 'csvColumn';
|
||||
|
||||
constructor(
|
||||
|
||||
@ -20,115 +20,101 @@
|
||||
</div>
|
||||
<div class="red-content-inner">
|
||||
<div class="content-container">
|
||||
<div class="header-item">
|
||||
<span class="all-caps-label">
|
||||
{{ 'audit-screen.table-header.title' | translate: { length: logs?.totalHits || 0 } }}
|
||||
</span>
|
||||
<div class="actions-wrapper">
|
||||
<redaction-pagination
|
||||
(pageChanged)="pageChanged($event)"
|
||||
[settings]="{ currentPage: logs?.page || 0, totalPages: totalPages }"
|
||||
></redaction-pagination>
|
||||
<div class="separator">·</div>
|
||||
<form [formGroup]="filterForm">
|
||||
<div class="iqser-input-group w-150 mr-20">
|
||||
<mat-form-field class="no-label">
|
||||
<mat-select formControlName="category">
|
||||
<mat-option *ngFor="let category of categories" [value]="category">
|
||||
{{ translations[category] | translate }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="iqser-input-group w-150">
|
||||
<mat-form-field class="no-label">
|
||||
<mat-select formControlName="userId">
|
||||
<mat-select-trigger>
|
||||
<redaction-initials-avatar
|
||||
*ngIf="filterForm.get('userId').value !== ALL_USERS"
|
||||
[userId]="filterForm.get('userId').value"
|
||||
[withName]="true"
|
||||
size="small"
|
||||
></redaction-initials-avatar>
|
||||
<div *ngIf="filterForm.get('userId').value === ALL_USERS" [translate]="ALL_USERS"></div>
|
||||
</mat-select-trigger>
|
||||
<mat-option *ngFor="let userId of userIds" [value]="userId">
|
||||
<redaction-initials-avatar
|
||||
*ngIf="userId !== ALL_USERS"
|
||||
[userId]="userId"
|
||||
[withName]="true"
|
||||
size="small"
|
||||
></redaction-initials-avatar>
|
||||
<div *ngIf="userId === ALL_USERS" [translate]="ALL_USERS"></div>
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="separator">·</div>
|
||||
<div class="iqser-input-group datepicker-wrapper mr-20">
|
||||
<input [matDatepicker]="fromPicker" formControlName="from" placeholder="dd/mm/yy" />
|
||||
<mat-datepicker-toggle [for]="fromPicker" matSuffix>
|
||||
<mat-icon matDatepickerToggleIcon svgIcon="red:calendar"></mat-icon>
|
||||
</mat-datepicker-toggle>
|
||||
<mat-datepicker #fromPicker></mat-datepicker>
|
||||
</div>
|
||||
|
||||
<div class="mr-20" translate="audit-screen.to"></div>
|
||||
|
||||
<div class="iqser-input-group datepicker-wrapper">
|
||||
<input [matDatepicker]="toPicker" formControlName="to" placeholder="dd/mm/yy" />
|
||||
<mat-datepicker-toggle [for]="toPicker" matSuffix>
|
||||
<mat-icon matDatepickerToggleIcon svgIcon="red:calendar"></mat-icon>
|
||||
</mat-datepicker-toggle>
|
||||
<mat-datepicker #toPicker></mat-datepicker>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-header" iqserSyncWidth="table-item">
|
||||
<iqser-table-column-name
|
||||
[label]="'audit-screen.table-col-names.message' | translate"
|
||||
column="message"
|
||||
></iqser-table-column-name>
|
||||
<iqser-table-column-name
|
||||
[label]="'audit-screen.table-col-names.date' | translate"
|
||||
column="date"
|
||||
></iqser-table-column-name>
|
||||
<iqser-table-column-name
|
||||
[label]="'audit-screen.table-col-names.user' | translate"
|
||||
class="user-column"
|
||||
column="user"
|
||||
></iqser-table-column-name>
|
||||
<iqser-table-column-name
|
||||
[label]="'audit-screen.table-col-names.category' | translate"
|
||||
column="category"
|
||||
></iqser-table-column-name>
|
||||
<div class="scrollbar-placeholder"></div>
|
||||
</div>
|
||||
|
||||
<redaction-empty-state
|
||||
*ngIf="!logs?.totalHits"
|
||||
[text]="'audit-screen.no-data.title' | translate"
|
||||
icon="red:document"
|
||||
></redaction-empty-state>
|
||||
|
||||
<cdk-virtual-scroll-viewport [itemSize]="80" redactionHasScrollbar>
|
||||
<div *cdkVirtualFor="let log of logs?.data" class="table-item">
|
||||
<div>
|
||||
{{ log.message }}
|
||||
</div>
|
||||
<div class="small-label">
|
||||
{{ log.recordDate | date: 'd MMM. yyyy, hh:mm a' }}
|
||||
</div>
|
||||
<div class="user-column">
|
||||
<redaction-initials-avatar [userId]="log.userId" [withName]="true" size="small"></redaction-initials-avatar>
|
||||
</div>
|
||||
<div [translate]="translations[log.category]"></div>
|
||||
<div class="scrollbar-placeholder"></div>
|
||||
</div>
|
||||
</cdk-virtual-scroll-viewport>
|
||||
<iqser-table
|
||||
[headerTemplate]="headerTemplate"
|
||||
[itemSize]="80"
|
||||
[noDataIcon]="'red:document'"
|
||||
[noDataText]="'audit-screen.no-data.title' | translate"
|
||||
[totalSize]="logs?.totalHits || 0"
|
||||
>
|
||||
</iqser-table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<ng-template #headerTemplate>
|
||||
<div class="table-header-actions">
|
||||
<redaction-pagination
|
||||
(pageChanged)="pageChanged($event)"
|
||||
[settings]="{ currentPage: logs?.page || 0, totalPages: totalPages }"
|
||||
class="mr-0"
|
||||
></redaction-pagination>
|
||||
<div class="separator">·</div>
|
||||
<form [formGroup]="filterForm">
|
||||
<div class="iqser-input-group w-150 mr-20">
|
||||
<mat-form-field class="no-label">
|
||||
<mat-select formControlName="category">
|
||||
<mat-option *ngFor="let category of categories" [value]="category">
|
||||
{{ translations[category] | translate }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="iqser-input-group w-150">
|
||||
<mat-form-field class="no-label">
|
||||
<mat-select formControlName="userId">
|
||||
<mat-select-trigger>
|
||||
<redaction-initials-avatar
|
||||
*ngIf="filterForm.get('userId').value !== ALL_USERS"
|
||||
[userId]="filterForm.get('userId').value"
|
||||
[withName]="true"
|
||||
size="small"
|
||||
></redaction-initials-avatar>
|
||||
<div *ngIf="filterForm.get('userId').value === ALL_USERS" [translate]="ALL_USERS"></div>
|
||||
</mat-select-trigger>
|
||||
<mat-option *ngFor="let userId of userIds" [value]="userId">
|
||||
<redaction-initials-avatar
|
||||
*ngIf="userId !== ALL_USERS"
|
||||
[userId]="userId"
|
||||
[withName]="true"
|
||||
size="small"
|
||||
></redaction-initials-avatar>
|
||||
<div *ngIf="userId === ALL_USERS" [translate]="ALL_USERS"></div>
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="separator">·</div>
|
||||
<div class="iqser-input-group datepicker-wrapper mr-20">
|
||||
<input [matDatepicker]="fromPicker" formControlName="from" placeholder="dd/mm/yy" />
|
||||
<mat-datepicker-toggle [for]="fromPicker" matSuffix>
|
||||
<mat-icon matDatepickerToggleIcon svgIcon="red:calendar"></mat-icon>
|
||||
</mat-datepicker-toggle>
|
||||
<mat-datepicker #fromPicker></mat-datepicker>
|
||||
</div>
|
||||
|
||||
<div class="mr-20" translate="audit-screen.to"></div>
|
||||
|
||||
<div class="iqser-input-group datepicker-wrapper">
|
||||
<input [matDatepicker]="toPicker" formControlName="to" placeholder="dd/mm/yy" />
|
||||
<mat-datepicker-toggle [for]="toPicker" matSuffix>
|
||||
<mat-icon matDatepickerToggleIcon svgIcon="red:calendar"></mat-icon>
|
||||
</mat-datepicker-toggle>
|
||||
<mat-datepicker #toPicker></mat-datepicker>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #messageTemplate let-log="entity">
|
||||
<div class="cell">
|
||||
{{ log.message }}
|
||||
</div>
|
||||
</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,44 +1,27 @@
|
||||
.content-container {
|
||||
.header-item {
|
||||
justify-content: space-between;
|
||||
}
|
||||
:host ::ng-deep iqser-table iqser-table-header .header-item {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.actions-wrapper,
|
||||
form {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
form {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.iqser-input-group {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
|
||||
.separator {
|
||||
margin: 0 20px;
|
||||
font-weight: bold;
|
||||
font-size: 16px;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.mr-20 {
|
||||
margin-right: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
cdk-virtual-scroll-viewport {
|
||||
::ng-deep.cdk-virtual-scroll-content-wrapper {
|
||||
grid-template-columns: 1fr 1fr 1fr 1fr 11px;
|
||||
|
||||
.table-item {
|
||||
> div {
|
||||
padding: 0 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.has-scrollbar:hover {
|
||||
::ng-deep.cdk-virtual-scroll-content-wrapper {
|
||||
grid-template-columns: 1fr 1fr 1fr 1fr;
|
||||
}
|
||||
}
|
||||
.iqser-input-group {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.separator {
|
||||
margin: 0 20px;
|
||||
font-weight: bold;
|
||||
font-size: 16px;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.mr-0 {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.mr-20 {
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
@ -1,41 +1,49 @@
|
||||
import { Component, OnDestroy } from '@angular/core';
|
||||
import { Component, forwardRef, Injector, OnDestroy, OnInit, TemplateRef, ViewChild } from '@angular/core';
|
||||
import { FormBuilder, FormGroup } from '@angular/forms';
|
||||
import { AuditControllerService, AuditResponse, AuditSearchRequest } from '@redaction/red-ui-http';
|
||||
import { AuditControllerService, AuditModel, AuditResponse, AuditSearchRequest } from '@redaction/red-ui-http';
|
||||
import { Moment } from 'moment';
|
||||
import { applyIntervalConstraints } from '@utils/date-inputs-utils';
|
||||
import { AutoUnsubscribe, LoadingService } from '@iqser/common-ui';
|
||||
import { DefaultListingServices, KeysOf, ListingComponent, LoadingService, TableColumnConfig } from '@iqser/common-ui';
|
||||
import { auditCategoriesTranslations } from '../../translations/audit-categories-translations';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { UserService } from '@services/user.service';
|
||||
import { AuditModelWrapper } from '../../../../models/audit-model-wrapper.model';
|
||||
|
||||
const PAGE_SIZE = 50;
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-audit-screen',
|
||||
templateUrl: './audit-screen.component.html',
|
||||
styleUrls: ['./audit-screen.component.scss']
|
||||
styleUrls: ['./audit-screen.component.scss'],
|
||||
providers: [...DefaultListingServices, { provide: ListingComponent, useExisting: forwardRef(() => AuditScreenComponent) }]
|
||||
})
|
||||
export class AuditScreenComponent extends AutoUnsubscribe implements OnDestroy {
|
||||
export class AuditScreenComponent extends ListingComponent<AuditModelWrapper> implements OnDestroy, OnInit {
|
||||
readonly ALL_CATEGORIES = 'allCategories';
|
||||
readonly ALL_USERS = _('audit-screen.all-users');
|
||||
readonly translations = auditCategoriesTranslations;
|
||||
readonly currentUser = this._userService.currentUser;
|
||||
|
||||
@ViewChild('messageTemplate', { static: true }) messageTemplate: TemplateRef<never>;
|
||||
@ViewChild('dateTemplate', { static: true }) dateTemplate: TemplateRef<never>;
|
||||
@ViewChild('userTemplate', { static: true }) userTemplate: TemplateRef<never>;
|
||||
@ViewChild('categoryTemplate', { static: true }) categoryTemplate: TemplateRef<never>;
|
||||
filterForm: FormGroup;
|
||||
categories: string[] = [];
|
||||
userIds: Set<string>;
|
||||
logs: AuditResponse;
|
||||
|
||||
tableColumnConfigs: TableColumnConfig<AuditModelWrapper>[];
|
||||
readonly tableHeaderLabel = _('audit-screen.table-header.title');
|
||||
protected readonly _primaryKey: KeysOf<AuditModelWrapper> = 'recordDate';
|
||||
private _previousFrom: Moment;
|
||||
private _previousTo: Moment;
|
||||
|
||||
constructor(
|
||||
private readonly _userService: UserService,
|
||||
protected readonly _injector: Injector,
|
||||
private readonly _formBuilder: FormBuilder,
|
||||
private readonly _loadingService: LoadingService,
|
||||
private readonly _auditControllerService: AuditControllerService
|
||||
) {
|
||||
super();
|
||||
super(_injector);
|
||||
this.filterForm = this._formBuilder.group({
|
||||
category: [this.ALL_CATEGORIES],
|
||||
userId: [this.ALL_USERS],
|
||||
@ -43,13 +51,11 @@ export class AuditScreenComponent extends AutoUnsubscribe implements OnDestroy {
|
||||
to: []
|
||||
});
|
||||
|
||||
this.addSubscription = this.filterForm.valueChanges.subscribe(value => {
|
||||
this.addSubscription = this.filterForm.valueChanges.subscribe(async value => {
|
||||
if (!this._updateDateFilters(value)) {
|
||||
this._fetchData();
|
||||
await this._fetchData();
|
||||
}
|
||||
});
|
||||
|
||||
this._fetchData();
|
||||
}
|
||||
|
||||
get totalPages(): number {
|
||||
@ -59,8 +65,35 @@ export class AuditScreenComponent extends AutoUnsubscribe implements OnDestroy {
|
||||
return Math.ceil(this.logs.totalHits / PAGE_SIZE);
|
||||
}
|
||||
|
||||
pageChanged(page: number) {
|
||||
this._fetchData(page);
|
||||
async pageChanged(page: number) {
|
||||
await this._fetchData(page);
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
this._configureTableColumns();
|
||||
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 {
|
||||
@ -73,7 +106,7 @@ export class AuditScreenComponent extends AutoUnsubscribe implements OnDestroy {
|
||||
return false;
|
||||
}
|
||||
|
||||
private _fetchData(page?: number) {
|
||||
private async _fetchData(page?: number) {
|
||||
this._loadingService.start();
|
||||
const promises = [];
|
||||
const category = this.filterForm.get('category').value;
|
||||
@ -96,15 +129,16 @@ export class AuditScreenComponent extends AutoUnsubscribe implements OnDestroy {
|
||||
promises.push(this._auditControllerService.getAuditCategories().toPromise());
|
||||
promises.push(this._auditControllerService.searchAuditLog(logsRequestBody).toPromise());
|
||||
|
||||
Promise.all(promises).then(data => {
|
||||
this.categories = data[0].map(c => c.category);
|
||||
this.categories.splice(0, 0, this.ALL_CATEGORIES);
|
||||
this.logs = data[1];
|
||||
this.userIds = new Set<string>([this.ALL_USERS]);
|
||||
for (const id of this.logs.data.map(log => log.userId).filter(uid => !!uid)) {
|
||||
this.userIds.add(id);
|
||||
}
|
||||
this._loadingService.stop();
|
||||
});
|
||||
const data = await Promise.all(promises);
|
||||
this.categories = data[0].map(c => c.category);
|
||||
this.categories.splice(0, 0, this.ALL_CATEGORIES);
|
||||
this.logs = data[1];
|
||||
const entities = this.logs.data.map((log: AuditModel) => new AuditModelWrapper(log));
|
||||
this.entitiesService.setEntities(entities);
|
||||
this.userIds = new Set<string>([this.ALL_USERS]);
|
||||
for (const id of this.logs.data.map(log => log.userId).filter(uid => !!uid)) {
|
||||
this.userIds.add(id);
|
||||
}
|
||||
this._loadingService.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@ -20,36 +20,31 @@
|
||||
<redaction-admin-side-nav type="dossierTemplates"></redaction-admin-side-nav>
|
||||
|
||||
<div class="content-container">
|
||||
<iqser-table-header
|
||||
[hasEmptyColumn]="true"
|
||||
[tableColumnConfigs]="tableColumnConfigs"
|
||||
[tableHeaderLabel]="tableHeaderLabel"
|
||||
></iqser-table-header>
|
||||
|
||||
<cdk-virtual-scroll-viewport [itemSize]="80" redactionHasScrollbar>
|
||||
<div *cdkVirtualFor="let color of sortedDisplayedEntities$ | async; trackBy: trackByPrimaryKey" class="table-item">
|
||||
<div>
|
||||
<div [translate]="translations[color.key]" class="table-item-title heading"></div>
|
||||
</div>
|
||||
|
||||
<div class="color-wrapper">
|
||||
<div [ngStyle]="{ 'background-color': color.value }" class="color-square"></div>
|
||||
</div>
|
||||
|
||||
<div class="actions-container">
|
||||
<div class="action-buttons">
|
||||
<iqser-circle-button
|
||||
(action)="openEditColorDialog($event, color)"
|
||||
*ngIf="currentUser.isAdmin"
|
||||
[tooltip]="'default-colors-screen.action.edit' | translate"
|
||||
icon="iqser:edit"
|
||||
[type]="circleButtonTypes.dark"
|
||||
></iqser-circle-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="scrollbar-placeholder"></div>
|
||||
</div>
|
||||
</cdk-virtual-scroll-viewport>
|
||||
<iqser-table [actionsTemplate]="actionsTemplate" [itemSize]="80" emptyColumnWidth="2fr"></iqser-table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<ng-template #nameTemplate let-color="entity">
|
||||
<div class="cell">
|
||||
<div [translate]="translations[color.key]" class="table-item-title heading"></div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #colorTemplate let-color="entity">
|
||||
<div class="cell color-wrapper">
|
||||
<div [ngStyle]="{ 'background-color': color.value }" class="color-square"></div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #actionsTemplate let-color="entity">
|
||||
<div class="action-buttons">
|
||||
<iqser-circle-button
|
||||
(action)="openEditColorDialog($event, color)"
|
||||
*ngIf="currentUser.isAdmin"
|
||||
[tooltip]="'default-colors-screen.action.edit' | translate"
|
||||
[type]="circleButtonTypes.dark"
|
||||
icon="iqser:edit"
|
||||
></iqser-circle-button>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
@ -1,30 +1,15 @@
|
||||
.content-container cdk-virtual-scroll-viewport {
|
||||
::ng-deep.cdk-virtual-scroll-content-wrapper {
|
||||
grid-template-columns: 2fr 1fr 2fr 11px;
|
||||
:host ::ng-deep iqser-table cdk-virtual-scroll-viewport .cdk-virtual-scroll-content-wrapper .table-item > div.cell {
|
||||
&:not(.scrollbar-placeholder) {
|
||||
padding-left: 24px;
|
||||
}
|
||||
|
||||
.table-item {
|
||||
> div:not(.scrollbar-placeholder) {
|
||||
padding-left: 24px;
|
||||
}
|
||||
&.color-wrapper {
|
||||
align-items: center;
|
||||
|
||||
.color-wrapper {
|
||||
align-items: center;
|
||||
|
||||
.color-square {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
min-width: 16px;
|
||||
}
|
||||
}
|
||||
.color-square {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
min-width: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
&.has-scrollbar:hover ::ng-deep.cdk-virtual-scroll-content-wrapper {
|
||||
grid-template-columns: 2fr 1fr 2fr;
|
||||
}
|
||||
}
|
||||
|
||||
.page-header .actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { ChangeDetectionStrategy, Component, Injector, OnInit } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, Component, forwardRef, Injector, OnInit, TemplateRef, ViewChild } from '@angular/core';
|
||||
import { AppStateService } from '@state/app-state.service';
|
||||
import { Colors, DictionaryControllerService } from '@redaction/red-ui-http';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
@ -18,20 +18,16 @@ interface ListItem extends Listable {
|
||||
templateUrl: './default-colors-screen.component.html',
|
||||
styleUrls: ['./default-colors-screen.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
providers: [...DefaultListingServices]
|
||||
providers: [...DefaultListingServices, { provide: ListingComponent, useExisting: forwardRef(() => DefaultColorsScreenComponent) }]
|
||||
})
|
||||
export class DefaultColorsScreenComponent extends ListingComponent<ListItem> implements OnInit {
|
||||
@ViewChild('nameTemplate', { static: true }) nameTemplate: TemplateRef<never>;
|
||||
@ViewChild('colorTemplate', { static: true }) colorTemplate: TemplateRef<never>;
|
||||
readonly circleButtonTypes = CircleButtonTypes;
|
||||
readonly currentUser = this._userService.currentUser;
|
||||
readonly translations = defaultColorsTranslations;
|
||||
readonly tableHeaderLabel = _('default-colors-screen.table-header.title');
|
||||
readonly tableColumnConfigs: TableColumnConfig<ListItem>[] = [
|
||||
{
|
||||
label: _('default-colors-screen.table-col-names.key'),
|
||||
sortByKey: 'key'
|
||||
},
|
||||
{ label: _('default-colors-screen.table-col-names.color'), class: 'flex-center' }
|
||||
];
|
||||
tableColumnConfigs: TableColumnConfig<ListItem>[];
|
||||
protected readonly _primaryKey = 'key';
|
||||
private _colorsObj: Colors;
|
||||
|
||||
@ -49,6 +45,7 @@ export class DefaultColorsScreenComponent extends ListingComponent<ListItem> imp
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
this._configureTableColumns();
|
||||
await this._loadColors();
|
||||
}
|
||||
|
||||
@ -68,6 +65,22 @@ export class DefaultColorsScreenComponent extends ListingComponent<ListItem> imp
|
||||
);
|
||||
}
|
||||
|
||||
private _configureTableColumns() {
|
||||
this.tableColumnConfigs = [
|
||||
{
|
||||
label: _('default-colors-screen.table-col-names.key'),
|
||||
sortByKey: 'key',
|
||||
template: this.nameTemplate,
|
||||
width: '2fr'
|
||||
},
|
||||
{
|
||||
label: _('default-colors-screen.table-col-names.color'),
|
||||
class: 'flex-center',
|
||||
template: this.colorTemplate
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
private async _loadColors() {
|
||||
this._loadingService.start();
|
||||
const data = await this._dictionaryControllerService.getColors(this._appStateService.activeDossierTemplateId).toPromise();
|
||||
|
||||
@ -20,88 +20,23 @@
|
||||
<redaction-admin-side-nav type="dossierTemplates"></redaction-admin-side-nav>
|
||||
|
||||
<div class="content-container">
|
||||
<iqser-table-header
|
||||
<iqser-table
|
||||
(noDataAction)="openAddEditDictionaryDialog()"
|
||||
[actionsTemplate]="actionsTemplate"
|
||||
[bulkActions]="bulkActions"
|
||||
[headerTemplate]="headerTemplate"
|
||||
[itemSize]="80"
|
||||
[noDataButtonLabel]="'dictionary-listing.no-data.action' | translate"
|
||||
[noDataText]="'dictionary-listing.no-data.title' | translate"
|
||||
[noMatchText]="'dictionary-listing.no-match.title' | translate"
|
||||
[selectionEnabled]="true"
|
||||
[hasEmptyColumn]="true"
|
||||
[tableColumnConfigs]="tableColumnConfigs"
|
||||
[tableHeaderLabel]="tableHeaderLabel"
|
||||
></iqser-table-header>
|
||||
|
||||
<redaction-empty-state
|
||||
(action)="openAddEditDictionaryDialog()"
|
||||
*ngIf="entitiesService.noData$ | async"
|
||||
[buttonLabel]="'dictionary-listing.no-data.action' | translate"
|
||||
[showButton]="currentUser.isAdmin"
|
||||
[text]="'dictionary-listing.no-data.title' | translate"
|
||||
icon="red:dictionary"
|
||||
></redaction-empty-state>
|
||||
|
||||
<redaction-empty-state
|
||||
*ngIf="noMatch$ | async"
|
||||
[text]="'dictionary-listing.no-match.title' | translate"
|
||||
></redaction-empty-state>
|
||||
|
||||
<cdk-virtual-scroll-viewport [itemSize]="80" redactionHasScrollbar>
|
||||
<div
|
||||
*cdkVirtualFor="let dict of sortedDisplayedEntities$ | async; trackBy: trackByPrimaryKey"
|
||||
[routerLink]="[dict.type]"
|
||||
class="table-item pointer"
|
||||
>
|
||||
<div (click)="toggleEntitySelected($event, dict)" class="selection-column">
|
||||
<iqser-round-checkbox [active]="isSelected(dict)"></iqser-round-checkbox>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div [ngStyle]="{ 'background-color': dict.hexColor }" class="color-square"></div>
|
||||
<div class="dict-name">
|
||||
<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="center small-label">
|
||||
{{ dict.rank }}
|
||||
</div>
|
||||
|
||||
<div class="center">
|
||||
<redaction-annotation-icon [dictType]="dict" [type]="dict.hint ? 'circle' : 'square'"></redaction-annotation-icon>
|
||||
</div>
|
||||
|
||||
<div class="actions-container">
|
||||
<div *ngIf="currentUser.isAdmin" class="action-buttons">
|
||||
<iqser-circle-button
|
||||
(action)="openDeleteDictionariesDialog($event, [dict])"
|
||||
[tooltip]="'dictionary-listing.action.delete' | translate"
|
||||
icon="red:trash"
|
||||
[type]="circleButtonTypes.dark"
|
||||
></iqser-circle-button>
|
||||
|
||||
<iqser-circle-button
|
||||
(action)="openAddEditDictionaryDialog($event, dict)"
|
||||
[tooltip]="'dictionary-listing.action.edit' | translate"
|
||||
icon="iqser:edit"
|
||||
[type]="circleButtonTypes.dark"
|
||||
></iqser-circle-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="scrollbar-placeholder"></div>
|
||||
</div>
|
||||
</cdk-virtual-scroll-viewport>
|
||||
[showNoDataButton]="currentUser.isAdmin"
|
||||
emptyColumnWidth="1fr"
|
||||
noDataIcon="red:dictionary"
|
||||
></iqser-table>
|
||||
</div>
|
||||
|
||||
<div class="right-container" redactionHasScrollbar>
|
||||
<div class="right-container" iqserHasScrollbar>
|
||||
<redaction-simple-doughnut-chart
|
||||
*ngIf="(entitiesService.noData$ | async) === false"
|
||||
[config]="chartData"
|
||||
@ -120,11 +55,13 @@
|
||||
(action)="openDeleteDictionariesDialog($event)"
|
||||
*ngIf="currentUser.isAdmin && (entitiesService.areSomeSelected$ | async)"
|
||||
[tooltip]="'dictionary-listing.bulk.delete' | translate"
|
||||
icon="red:trash"
|
||||
[type]="circleButtonTypes.dark"
|
||||
icon="red:trash"
|
||||
></iqser-circle-button>
|
||||
</ng-template>
|
||||
|
||||
<div class="attributes-actions-container">
|
||||
<ng-template #headerTemplate>
|
||||
<div class="table-header-actions">
|
||||
<iqser-input-with-action
|
||||
[(value)]="searchService.searchValue"
|
||||
[placeholder]="'dictionary-listing.search' | translate"
|
||||
@ -134,9 +71,60 @@
|
||||
(action)="openAddEditDictionaryDialog()"
|
||||
*ngIf="currentUser.isAdmin"
|
||||
[label]="'dictionary-listing.add-new' | translate"
|
||||
icon="red:plus"
|
||||
[type]="iconButtonTypes.primary"
|
||||
icon="red:plus"
|
||||
></iqser-icon-button>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #labelTemplate let-entity="entity">
|
||||
<div class="cell">
|
||||
<div [ngStyle]="{ 'background-color': entity.hexColor }" class="color-square"></div>
|
||||
<div class="dict-name">
|
||||
<div class="table-item-title heading">
|
||||
{{ entity.label }}
|
||||
</div>
|
||||
<div class="small-label stats-subtitle">
|
||||
<div>
|
||||
<mat-icon svgIcon="red:entries"></mat-icon>
|
||||
{{ entity.entries?.length }}
|
||||
</div>
|
||||
<div *ngIf="!entity.caseInsensitive">
|
||||
<mat-icon svgIcon="red:case-sensitive"></mat-icon>
|
||||
{{ 'dictionary-listing.case-sensitive' | translate }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</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,58 +1,39 @@
|
||||
iqser-table-header::ng-deep .header-item {
|
||||
padding-right: 16px;
|
||||
}
|
||||
:host {
|
||||
::ng-deep iqser-table cdk-virtual-scroll-viewport .cdk-virtual-scroll-content-wrapper .table-item > div.cell {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
|
||||
.content-container cdk-virtual-scroll-viewport {
|
||||
::ng-deep.cdk-virtual-scroll-content-wrapper {
|
||||
grid-template-columns: auto 2fr 1fr 1fr 1fr 11px;
|
||||
&.center {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.table-item > div:not(.scrollbar-placeholder) {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding-left: 10px;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
.color-square {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
min-width: 16px;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
&.center {
|
||||
justify-content: center;
|
||||
}
|
||||
.dict-name {
|
||||
z-index: 1;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
.stats-subtitle {
|
||||
margin-top: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
&.has-scrollbar:hover ::ng-deep.cdk-virtual-scroll-content-wrapper {
|
||||
grid-template-columns: auto 2fr 1fr 1fr 1fr;
|
||||
.right-container {
|
||||
display: flex;
|
||||
width: 353px;
|
||||
min-width: 353px;
|
||||
justify-content: center;
|
||||
padding: 50px 20px 30px 20px;
|
||||
|
||||
&.has-scrollbar:hover {
|
||||
padding-right: 9px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.right-container {
|
||||
display: flex;
|
||||
width: 353px;
|
||||
min-width: 353px;
|
||||
justify-content: center;
|
||||
padding: 50px 20px 30px 20px;
|
||||
|
||||
&.has-scrollbar:hover {
|
||||
padding-right: 9px;
|
||||
}
|
||||
}
|
||||
|
||||
.page-header .actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Component, Injector, OnInit } from '@angular/core';
|
||||
import { Component, forwardRef, Injector, OnInit, TemplateRef, ViewChild } from '@angular/core';
|
||||
import { DoughnutChartConfig } from '@shared/components/simple-doughnut-chart/simple-doughnut-chart.component';
|
||||
import { DictionaryControllerService } from '@redaction/red-ui-http';
|
||||
import { AppStateService } from '@state/app-state.service';
|
||||
@ -29,29 +29,18 @@ const toChartConfig = (dict: TypeValueWrapper): DoughnutChartConfig => ({
|
||||
@Component({
|
||||
templateUrl: './dictionary-listing-screen.component.html',
|
||||
styleUrls: ['./dictionary-listing-screen.component.scss'],
|
||||
providers: [...DefaultListingServices]
|
||||
providers: [...DefaultListingServices, { provide: ListingComponent, useExisting: forwardRef(() => DictionaryListingScreenComponent) }]
|
||||
})
|
||||
export class DictionaryListingScreenComponent extends ListingComponent<TypeValueWrapper> implements OnInit {
|
||||
readonly iconButtonTypes = IconButtonTypes;
|
||||
readonly circleButtonTypes = CircleButtonTypes;
|
||||
readonly currentUser = this._userService.currentUser;
|
||||
readonly tableHeaderLabel = _('dictionary-listing.table-header.title');
|
||||
readonly tableColumnConfigs: TableColumnConfig<TypeValueWrapper>[] = [
|
||||
{
|
||||
label: _('dictionary-listing.table-col-names.type'),
|
||||
sortByKey: 'label'
|
||||
},
|
||||
{
|
||||
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'
|
||||
}
|
||||
];
|
||||
tableColumnConfigs: TableColumnConfig<TypeValueWrapper>[];
|
||||
chartData: DoughnutChartConfig[] = [];
|
||||
@ViewChild('labelTemplate', { static: true }) labelTemplate: TemplateRef<never>;
|
||||
@ViewChild('rankTemplate', { static: true }) rankTemplate: TemplateRef<never>;
|
||||
@ViewChild('iconTemplate', { static: true }) iconTemplate: TemplateRef<never>;
|
||||
protected readonly _primaryKey = 'type';
|
||||
|
||||
constructor(
|
||||
@ -69,7 +58,10 @@ export class DictionaryListingScreenComponent extends ListingComponent<TypeValue
|
||||
_appStateService.activateDossierTemplate(_activatedRoute.snapshot.params.dossierTemplateId);
|
||||
}
|
||||
|
||||
routerLinkFn = (entity: TypeValueWrapper) => [entity.type];
|
||||
|
||||
ngOnInit(): void {
|
||||
this._configureTableColumns();
|
||||
this._loadDictionaryData();
|
||||
}
|
||||
|
||||
@ -108,6 +100,28 @@ export class DictionaryListingScreenComponent extends ListingComponent<TypeValue
|
||||
);
|
||||
}
|
||||
|
||||
private _configureTableColumns() {
|
||||
this.tableColumnConfigs = [
|
||||
{
|
||||
label: _('dictionary-listing.table-col-names.type'),
|
||||
sortByKey: 'label',
|
||||
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 {
|
||||
const appStateDictionaryData = this._appStateService.dictionaryData[this._appStateService.activeDossierTemplateId];
|
||||
const entities = Object.values(appStateDictionaryData).filter(d => !d.virtual);
|
||||
|
||||
@ -16,13 +16,13 @@
|
||||
<form *ngIf="digitalSignatureForm" [formGroup]="digitalSignatureForm" autocomplete="off">
|
||||
<input #fileInput (change)="fileChanged($event, fileInput)" class="file-upload-input" hidden type="file" />
|
||||
|
||||
<redaction-empty-state
|
||||
<iqser-empty-state
|
||||
(action)="fileInput.click()"
|
||||
*ngIf="!hasDigitalSignatureSet"
|
||||
[buttonLabel]="'digital-signature-screen.no-data.action' | translate"
|
||||
[text]="'digital-signature-screen.no-data.title' | translate"
|
||||
buttonIcon="red:upload"
|
||||
></redaction-empty-state>
|
||||
></iqser-empty-state>
|
||||
|
||||
<div [class.hidden]="!hasDigitalSignatureSet" class="iqser-input-group required w-300">
|
||||
<label translate="digital-signature-screen.certificate-name.label"></label>
|
||||
|
||||
@ -20,68 +20,20 @@
|
||||
<redaction-admin-side-nav type="dossierTemplates"></redaction-admin-side-nav>
|
||||
|
||||
<div class="content-container">
|
||||
<iqser-table-header
|
||||
<iqser-table
|
||||
(noDataAction)="openAddEditAttributeDialog(null)"
|
||||
[actionsTemplate]="actionsTemplate"
|
||||
[bulkActions]="bulkActions"
|
||||
[headerTemplate]="headerTemplate"
|
||||
[itemSize]="50"
|
||||
[noDataButtonLabel]="'dossier-attributes-listing.no-data.action' | translate"
|
||||
[noDataText]="'dossier-attributes-listing.no-data.title' | translate"
|
||||
[noMatchText]="'dossier-attributes-listing.no-match.title' | translate"
|
||||
[selectionEnabled]="true"
|
||||
[hasEmptyColumn]="true"
|
||||
[tableColumnConfigs]="tableColumnConfigs"
|
||||
[tableHeaderLabel]="tableHeaderLabel"
|
||||
></iqser-table-header>
|
||||
|
||||
<redaction-empty-state
|
||||
(action)="openAddEditAttributeDialog($event)"
|
||||
*ngIf="entitiesService.noData$ | async"
|
||||
[buttonLabel]="'dossier-attributes-listing.no-data.action' | translate"
|
||||
[showButton]="currentUser.isAdmin"
|
||||
[text]="'dossier-attributes-listing.no-data.title' | translate"
|
||||
icon="red:attribute"
|
||||
></redaction-empty-state>
|
||||
|
||||
<redaction-empty-state
|
||||
*ngIf="noMatch$ | async"
|
||||
[text]="'dossier-attributes-listing.no-match.title' | translate"
|
||||
></redaction-empty-state>
|
||||
|
||||
<cdk-virtual-scroll-viewport [itemSize]="50" redactionHasScrollbar>
|
||||
<div
|
||||
*cdkVirtualFor="let attribute of sortedDisplayedEntities$ | async; trackBy: trackByPrimaryKey"
|
||||
class="table-item pointer"
|
||||
>
|
||||
<div (click)="toggleEntitySelected($event, attribute)" class="selection-column">
|
||||
<iqser-round-checkbox [active]="isSelected(attribute)"></iqser-round-checkbox>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{{ attribute.label }}
|
||||
</div>
|
||||
|
||||
<div class="small-label">
|
||||
{{ attribute.placeholder }}
|
||||
</div>
|
||||
|
||||
<div class="small-label">
|
||||
{{ translations[attribute.type] | translate }}
|
||||
</div>
|
||||
<div class="actions-container">
|
||||
<div *ngIf="currentUser.isAdmin" class="action-buttons">
|
||||
<iqser-circle-button
|
||||
(action)="openAddEditAttributeDialog($event, attribute)"
|
||||
[tooltip]="'dossier-attributes-listing.action.edit' | translate"
|
||||
icon="iqser:edit"
|
||||
[type]="circleButtonTypes.dark"
|
||||
></iqser-circle-button>
|
||||
|
||||
<iqser-circle-button
|
||||
(action)="openConfirmDeleteAttributeDialog($event, attribute)"
|
||||
[tooltip]="'dossier-attributes-listing.action.delete' | translate"
|
||||
icon="red:trash"
|
||||
[type]="circleButtonTypes.dark"
|
||||
></iqser-circle-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="scrollbar-placeholder"></div>
|
||||
</div>
|
||||
</cdk-virtual-scroll-viewport>
|
||||
[showNoDataButton]="currentUser.isAdmin"
|
||||
emptyColumnWidth="1fr"
|
||||
noDataIcon="red:attribute"
|
||||
></iqser-table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@ -91,11 +43,13 @@
|
||||
(action)="openConfirmDeleteAttributeDialog($event)"
|
||||
*ngIf="currentUser.isAdmin && entitiesService.areSomeSelected$ | async"
|
||||
[tooltip]="'dossier-attributes-listing.bulk.delete' | translate"
|
||||
icon="red:trash"
|
||||
[type]="circleButtonTypes.dark"
|
||||
icon="red:trash"
|
||||
></iqser-circle-button>
|
||||
</ng-template>
|
||||
|
||||
<div class="attributes-actions-container">
|
||||
<ng-template #headerTemplate>
|
||||
<div class="table-header-actions">
|
||||
<iqser-input-with-action
|
||||
[(value)]="searchService.searchValue"
|
||||
[placeholder]="'dossier-attributes-listing.search' | translate"
|
||||
@ -105,8 +59,44 @@
|
||||
(action)="openAddEditAttributeDialog($event)"
|
||||
*ngIf="currentUser.isAdmin"
|
||||
[label]="'dossier-attributes-listing.add-new' | translate"
|
||||
icon="red:plus"
|
||||
[type]="iconButtonTypes.primary"
|
||||
icon="red:plus"
|
||||
></iqser-icon-button>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #actionsTemplate let-attribute="entity">
|
||||
<div *ngIf="currentUser.isAdmin" class="action-buttons">
|
||||
<iqser-circle-button
|
||||
(action)="openAddEditAttributeDialog($event, attribute)"
|
||||
[tooltip]="'dossier-attributes-listing.action.edit' | translate"
|
||||
[type]="circleButtonTypes.dark"
|
||||
icon="iqser:edit"
|
||||
></iqser-circle-button>
|
||||
|
||||
<iqser-circle-button
|
||||
(action)="openConfirmDeleteAttributeDialog($event, attribute)"
|
||||
[tooltip]="'dossier-attributes-listing.action.delete' | translate"
|
||||
[type]="circleButtonTypes.dark"
|
||||
icon="red:trash"
|
||||
></iqser-circle-button>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #labelTemplate let-attribute="entity">
|
||||
<div class="cell">
|
||||
{{ attribute.label }}
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #placeholderTemplate let-attribute="entity">
|
||||
<div class="cell small-label">
|
||||
{{ attribute.placeholder }}
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #typeTemplate let-attribute="entity">
|
||||
<div class="cell small-label">
|
||||
{{ translations[attribute.type] | translate }}
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
@ -1,34 +0,0 @@
|
||||
.page-header .actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
iqser-table-header::ng-deep .attributes-actions-container {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
justify-content: flex-end;
|
||||
|
||||
> *:not(:last-child) {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
cdk-virtual-scroll-viewport {
|
||||
::ng-deep.cdk-virtual-scroll-content-wrapper {
|
||||
grid-template-columns: auto 2fr 2fr 1fr 1fr 11px;
|
||||
|
||||
.table-item > div {
|
||||
height: 50px;
|
||||
|
||||
&:not(.scrollbar-placeholder) {
|
||||
padding-left: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.has-scrollbar:hover {
|
||||
::ng-deep.cdk-virtual-scroll-content-wrapper {
|
||||
grid-template-columns: auto 2fr 2fr 1fr 1fr;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
import { Component, Injector, OnInit } from '@angular/core';
|
||||
import { Component, forwardRef, Injector, OnInit, TemplateRef, ViewChild } from '@angular/core';
|
||||
import {
|
||||
CircleButtonTypes,
|
||||
DefaultListingServices,
|
||||
@ -19,7 +19,10 @@ import { UserService } from '@services/user.service';
|
||||
@Component({
|
||||
templateUrl: './dossier-attributes-listing-screen.component.html',
|
||||
styleUrls: ['./dossier-attributes-listing-screen.component.scss'],
|
||||
providers: [...DefaultListingServices]
|
||||
providers: [
|
||||
...DefaultListingServices,
|
||||
{ provide: ListingComponent, useExisting: forwardRef(() => DossierAttributesListingScreenComponent) }
|
||||
]
|
||||
})
|
||||
export class DossierAttributesListingScreenComponent extends ListingComponent<DossierAttributeConfig> implements OnInit {
|
||||
readonly iconButtonTypes = IconButtonTypes;
|
||||
@ -27,17 +30,10 @@ export class DossierAttributesListingScreenComponent extends ListingComponent<Do
|
||||
readonly currentUser = this._userService.currentUser;
|
||||
readonly translations = dossierAttributeTypesTranslations;
|
||||
readonly tableHeaderLabel = _('dossier-attributes-listing.table-header.title');
|
||||
readonly tableColumnConfigs: TableColumnConfig<DossierAttributeConfig>[] = [
|
||||
{
|
||||
label: _('dossier-attributes-listing.table-col-names.label'),
|
||||
sortByKey: 'label'
|
||||
},
|
||||
{ label: _('dossier-attributes-listing.table-col-names.placeholder') },
|
||||
{
|
||||
label: _('dossier-attributes-listing.table-col-names.type'),
|
||||
sortByKey: 'type'
|
||||
}
|
||||
];
|
||||
tableColumnConfigs: TableColumnConfig<DossierAttributeConfig>[];
|
||||
@ViewChild('labelTemplate', { static: true }) labelTemplate: TemplateRef<never>;
|
||||
@ViewChild('placeholderTemplate', { static: true }) placeholderTemplate: TemplateRef<never>;
|
||||
@ViewChild('typeTemplate', { static: true }) typeTemplate: TemplateRef<never>;
|
||||
protected readonly _primaryKey = 'label';
|
||||
|
||||
constructor(
|
||||
@ -54,6 +50,7 @@ export class DossierAttributesListingScreenComponent extends ListingComponent<Do
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
this._configureTableColumns();
|
||||
await this._loadData();
|
||||
}
|
||||
|
||||
@ -62,6 +59,7 @@ export class DossierAttributesListingScreenComponent extends ListingComponent<Do
|
||||
this._loadingService.start();
|
||||
const ids = dossierAttribute ? [dossierAttribute.id] : this.entitiesService.selected.map(item => item.id);
|
||||
await this._dossierAttributesService.deleteConfigs(ids);
|
||||
this.entitiesService.setSelected([]);
|
||||
await this._loadData();
|
||||
});
|
||||
}
|
||||
@ -77,6 +75,27 @@ 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() {
|
||||
this._loadingService.start();
|
||||
const attributes = await this._dossierAttributesService.getConfig();
|
||||
|
||||
@ -12,83 +12,15 @@
|
||||
|
||||
<div class="red-content-inner">
|
||||
<div class="content-container">
|
||||
<iqser-table-header
|
||||
<iqser-table
|
||||
[bulkActions]="bulkActions"
|
||||
[headerTemplate]="headerTemplate"
|
||||
[itemSize]="80"
|
||||
[noDataText]="'dossier-templates-listing.no-data.title' | translate"
|
||||
[noMatchText]="'dossier-templates-listing.no-match.title' | translate"
|
||||
[selectionEnabled]="true"
|
||||
[tableColumnConfigs]="tableColumnConfigs"
|
||||
[tableHeaderLabel]="tableHeaderLabel"
|
||||
>
|
||||
<div class="actions flex-1">
|
||||
<iqser-input-with-action
|
||||
[(value)]="searchService.searchValue"
|
||||
[placeholder]="'dossier-templates-listing.search' | translate"
|
||||
></iqser-input-with-action>
|
||||
|
||||
<iqser-icon-button
|
||||
(action)="openAddDossierTemplateDialog()"
|
||||
*ngIf="currentUser.isAdmin && userPreferenceService.areDevFeaturesEnabled"
|
||||
[label]="'dossier-templates-listing.add-new' | translate"
|
||||
[type]="iconButtonTypes.primary"
|
||||
icon="red:plus"
|
||||
></iqser-icon-button>
|
||||
</div>
|
||||
</iqser-table-header>
|
||||
|
||||
<redaction-empty-state
|
||||
*ngIf="entitiesService.noData$ | async"
|
||||
[text]="'dossier-templates-listing.no-data.title' | translate"
|
||||
icon="red:template"
|
||||
></redaction-empty-state>
|
||||
|
||||
<redaction-empty-state
|
||||
*ngIf="noMatch$ | async"
|
||||
[text]="'dossier-templates-listing.no-match.title' | translate"
|
||||
></redaction-empty-state>
|
||||
|
||||
<cdk-virtual-scroll-viewport [itemSize]="80" redactionHasScrollbar>
|
||||
<div
|
||||
*cdkVirtualFor="let dossierTemplate of sortedDisplayedEntities$ | async"
|
||||
[routerLink]="[dossierTemplate.dossierTemplateId, 'dictionaries']"
|
||||
class="table-item pointer"
|
||||
>
|
||||
<div (click)="toggleEntitySelected($event, dossierTemplate)" class="selection-column">
|
||||
<iqser-round-checkbox [active]="isSelected(dossierTemplate)"></iqser-round-checkbox>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="table-item-title heading">
|
||||
{{ dossierTemplate.name }}
|
||||
</div>
|
||||
<div class="small-label stats-subtitle">
|
||||
<div>
|
||||
<mat-icon svgIcon="red:dictionary"></mat-icon>
|
||||
{{
|
||||
'dossier-templates-listing.dictionaries' | translate: { length: dossierTemplate.dictionariesCount }
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="user-column">
|
||||
<redaction-initials-avatar [userId]="dossierTemplate.createdBy" [withName]="true"></redaction-initials-avatar>
|
||||
</div>
|
||||
<div class="small-label">
|
||||
{{ dossierTemplate.dateAdded | date: 'd MMM. yyyy' }}
|
||||
</div>
|
||||
<div>
|
||||
<div class="small-label">
|
||||
{{ dossierTemplate.dateModified | date: 'd MMM. yyyy' }}
|
||||
</div>
|
||||
|
||||
<redaction-dossier-template-actions
|
||||
(loadDossierTemplatesData)="loadDossierTemplatesData()"
|
||||
[dossierTemplateId]="dossierTemplate.dossierTemplateId"
|
||||
class="actions-container"
|
||||
></redaction-dossier-template-actions>
|
||||
</div>
|
||||
<div class="scrollbar-placeholder"></div>
|
||||
</div>
|
||||
</cdk-virtual-scroll-viewport>
|
||||
noDataIcon="red:template"
|
||||
></iqser-table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -103,3 +35,63 @@
|
||||
icon="red:trash"
|
||||
></iqser-circle-button>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #actionsTemplate let-dossierTemplate="entity">
|
||||
<redaction-dossier-template-actions
|
||||
(loadDossierTemplatesData)="loadDossierTemplatesData()"
|
||||
[dossierTemplateId]="dossierTemplate.dossierTemplateId"
|
||||
class="actions-container"
|
||||
></redaction-dossier-template-actions>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #headerTemplate>
|
||||
<div class="table-header-actions">
|
||||
<iqser-input-with-action
|
||||
[(value)]="searchService.searchValue"
|
||||
[placeholder]="'dossier-templates-listing.search' | translate"
|
||||
></iqser-input-with-action>
|
||||
|
||||
<iqser-icon-button
|
||||
(action)="openAddDossierTemplateDialog()"
|
||||
*ngIf="currentUser.isAdmin && userPreferenceService.areDevFeaturesEnabled"
|
||||
[label]="'dossier-templates-listing.add-new' | translate"
|
||||
[type]="iconButtonTypes.primary"
|
||||
icon="red:plus"
|
||||
></iqser-icon-button>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #nameTemplate let-dossierTemplate="entity">
|
||||
<div class="cell">
|
||||
<div class="table-item-title heading">
|
||||
{{ dossierTemplate.name }}
|
||||
</div>
|
||||
<div class="small-label stats-subtitle">
|
||||
<div>
|
||||
<mat-icon svgIcon="red:dictionary"></mat-icon>
|
||||
{{ 'dossier-templates-listing.dictionaries' | translate: { length: dossierTemplate.dictionariesCount } }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #userTemplate let-dossierTemplate="entity">
|
||||
<div class="cell user-column">
|
||||
<redaction-initials-avatar [userId]="dossierTemplate.createdBy" [withName]="true"></redaction-initials-avatar>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #dateAddedTemplate let-dossierTemplate="entity">
|
||||
<div class="cell small-label">
|
||||
{{ dossierTemplate.dateAdded | date: 'd MMM. yyyy' }}
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #dateModifiedTemplate let-dossierTemplate="entity">
|
||||
<div class="cell">
|
||||
<div class="small-label">
|
||||
{{ dossierTemplate.dateModified | date: 'd MMM. yyyy' }}
|
||||
</div>
|
||||
<ng-container *ngTemplateOutlet="actionsTemplate; context: { entity: dossierTemplate }"></ng-container>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
@ -1,29 +1,15 @@
|
||||
cdk-virtual-scroll-viewport {
|
||||
::ng-deep.cdk-virtual-scroll-content-wrapper {
|
||||
grid-template-columns: auto 1fr 1fr 1fr 1fr 11px;
|
||||
:host {
|
||||
::ng-deep iqser-table cdk-virtual-scroll-viewport .cdk-virtual-scroll-content-wrapper .table-item > div.cell {
|
||||
.stats-subtitle {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.table-item {
|
||||
> div:not(.scrollbar-placeholder) {
|
||||
padding-left: 10px;
|
||||
|
||||
.stats-subtitle {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.table-item-title {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
.table-item-title {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&.has-scrollbar:hover {
|
||||
::ng-deep.cdk-virtual-scroll-content-wrapper {
|
||||
grid-template-columns: auto 1fr 1fr 1fr 1fr;
|
||||
}
|
||||
.page-header .actions > *:not(:last-child) {
|
||||
margin-right: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.page-header .actions > *:not(:last-child) {
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { ChangeDetectionStrategy, Component, Injector, OnInit } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, Component, forwardRef, Injector, OnInit, TemplateRef, ViewChild } from '@angular/core';
|
||||
import { AppStateService } from '@state/app-state.service';
|
||||
import { UserPreferenceService } from '@services/user-preference.service';
|
||||
import { AdminDialogService } from '../../services/admin-dialog.service';
|
||||
@ -21,28 +21,21 @@ import { RouterHistoryService } from '@services/router-history.service';
|
||||
templateUrl: './dossier-templates-listing-screen.component.html',
|
||||
styleUrls: ['./dossier-templates-listing-screen.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
providers: [...DefaultListingServices]
|
||||
providers: [
|
||||
...DefaultListingServices,
|
||||
{ provide: ListingComponent, useExisting: forwardRef(() => DossierTemplatesListingScreenComponent) }
|
||||
]
|
||||
})
|
||||
export class DossierTemplatesListingScreenComponent extends ListingComponent<DossierTemplateModelWrapper> implements OnInit {
|
||||
readonly iconButtonTypes = IconButtonTypes;
|
||||
readonly circleButtonTypes = CircleButtonTypes;
|
||||
readonly currentUser = this._userService.currentUser;
|
||||
readonly tableHeaderLabel = _('dossier-templates-listing.table-header.title');
|
||||
readonly tableColumnConfigs: readonly TableColumnConfig<DossierTemplateModelWrapper>[] = [
|
||||
{
|
||||
label: _('dossier-templates-listing.table-col-names.name'),
|
||||
sortByKey: 'name'
|
||||
},
|
||||
{ label: _('dossier-templates-listing.table-col-names.created-by'), class: 'user-column' },
|
||||
{
|
||||
label: _('dossier-templates-listing.table-col-names.created-on'),
|
||||
sortByKey: 'dateAdded'
|
||||
},
|
||||
{
|
||||
label: _('dossier-templates-listing.table-col-names.modified-on'),
|
||||
sortByKey: 'dateModified'
|
||||
}
|
||||
];
|
||||
tableColumnConfigs: TableColumnConfig<DossierTemplateModelWrapper>[];
|
||||
@ViewChild('nameTemplate', { static: true }) nameTemplate: TemplateRef<never>;
|
||||
@ViewChild('userTemplate', { static: true }) userTemplate: TemplateRef<never>;
|
||||
@ViewChild('dateAddedTemplate', { static: true }) dateAddedTemplate: TemplateRef<never>;
|
||||
@ViewChild('dateModifiedTemplate', { static: true }) dateModifiedTemplate: TemplateRef<never>;
|
||||
protected readonly _primaryKey = 'name';
|
||||
|
||||
constructor(
|
||||
@ -59,7 +52,10 @@ export class DossierTemplatesListingScreenComponent extends ListingComponent<Dos
|
||||
super(_injector);
|
||||
}
|
||||
|
||||
routerLinkFn = (dossierTemplate: DossierTemplateModelWrapper) => [dossierTemplate.dossierTemplateId, 'dictionaries'];
|
||||
|
||||
ngOnInit(): void {
|
||||
this._configureTableColumns();
|
||||
this.loadDossierTemplatesData();
|
||||
}
|
||||
|
||||
@ -81,6 +77,31 @@ export class DossierTemplatesListingScreenComponent extends ListingComponent<Dos
|
||||
});
|
||||
}
|
||||
|
||||
private _configureTableColumns() {
|
||||
this.tableColumnConfigs = [
|
||||
{
|
||||
label: _('dossier-templates-listing.table-col-names.name'),
|
||||
sortByKey: 'name',
|
||||
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)) {
|
||||
await this._dossierTemplateControllerService
|
||||
.deleteDossierTemplates(templateIds)
|
||||
|
||||
@ -20,76 +20,17 @@
|
||||
<redaction-admin-side-nav type="dossierTemplates"></redaction-admin-side-nav>
|
||||
|
||||
<div class="content-container">
|
||||
<iqser-table-header
|
||||
<iqser-table
|
||||
[actionsTemplate]="actionsTemplate"
|
||||
[bulkActions]="bulkActions"
|
||||
[headerTemplate]="headerTemplate"
|
||||
[itemSize]="80"
|
||||
[noDataText]="'file-attributes-listing.no-data.title' | translate"
|
||||
[noMatchText]="'file-attributes-listing.no-match.title' | translate"
|
||||
[selectionEnabled]="true"
|
||||
[hasEmptyColumn]="true"
|
||||
[tableColumnConfigs]="tableColumnConfigs"
|
||||
[tableHeaderLabel]="tableHeaderLabel"
|
||||
></iqser-table-header>
|
||||
|
||||
<redaction-empty-state
|
||||
*ngIf="entitiesService.noData$ | async"
|
||||
[text]="'file-attributes-listing.no-data.title' | translate"
|
||||
icon="red:attribute"
|
||||
></redaction-empty-state>
|
||||
|
||||
<redaction-empty-state
|
||||
*ngIf="noMatch$ | async"
|
||||
[text]="'file-attributes-listing.no-match.title' | translate"
|
||||
></redaction-empty-state>
|
||||
|
||||
<cdk-virtual-scroll-viewport [itemSize]="80" redactionHasScrollbar>
|
||||
<div *cdkVirtualFor="let attribute of sortedDisplayedEntities$ | async" class="table-item">
|
||||
<div (click)="toggleEntitySelected($event, attribute)" class="selection-column">
|
||||
<iqser-round-checkbox [active]="isSelected(attribute)"></iqser-round-checkbox>
|
||||
</div>
|
||||
|
||||
<div class="label">
|
||||
<span>{{ attribute.label }}</span>
|
||||
</div>
|
||||
|
||||
<div [translate]="translations[attribute.type]" class="small-label"></div>
|
||||
|
||||
<div class="center read-only">
|
||||
<mat-icon
|
||||
*ngIf="!attribute.editable"
|
||||
[matTooltip]="'file-attributes-listing.read-only' | translate"
|
||||
matTooltipPosition="above"
|
||||
svgIcon="red:read-only"
|
||||
></mat-icon>
|
||||
</div>
|
||||
<div class="small-label">
|
||||
{{ attribute.csvColumnHeader }}
|
||||
</div>
|
||||
<div class="center">
|
||||
<iqser-round-checkbox *ngIf="attribute.filterable" [active]="true" [size]="18"></iqser-round-checkbox>
|
||||
</div>
|
||||
<div class="center">
|
||||
<iqser-round-checkbox *ngIf="attribute.displayedInFileList" [active]="true" [size]="18"></iqser-round-checkbox>
|
||||
</div>
|
||||
<div class="center">
|
||||
<iqser-round-checkbox *ngIf="attribute.primaryAttribute" [active]="true" [size]="18"></iqser-round-checkbox>
|
||||
</div>
|
||||
<div class="actions-container">
|
||||
<div *ngIf="currentUser.isAdmin" class="action-buttons">
|
||||
<iqser-circle-button
|
||||
(action)="openAddEditAttributeDialog($event, attribute)"
|
||||
[tooltip]="'file-attributes-listing.action.edit' | translate"
|
||||
icon="iqser:edit"
|
||||
[type]="circleButtonTypes.dark"
|
||||
></iqser-circle-button>
|
||||
<iqser-circle-button
|
||||
(action)="openConfirmDeleteAttributeDialog($event, attribute)"
|
||||
[tooltip]="'file-attributes-listing.action.delete' | translate"
|
||||
icon="red:trash"
|
||||
[type]="circleButtonTypes.dark"
|
||||
></iqser-circle-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="scrollbar-placeholder"></div>
|
||||
</div>
|
||||
</cdk-virtual-scroll-viewport>
|
||||
emptyColumnWidth="1fr"
|
||||
noDataIcon="red:attribute"
|
||||
></iqser-table>
|
||||
</div>
|
||||
|
||||
<div class="right-container"></div>
|
||||
@ -101,11 +42,13 @@
|
||||
(click)="openConfirmDeleteAttributeDialog($event)"
|
||||
*ngIf="currentUser.isAdmin && (entitiesService.areSomeSelected$ | async)"
|
||||
[tooltip]="'file-attributes-listing.bulk-actions.delete' | translate"
|
||||
icon="red:trash"
|
||||
[type]="circleButtonTypes.dark"
|
||||
icon="red:trash"
|
||||
></iqser-circle-button>
|
||||
</ng-template>
|
||||
|
||||
<div class="attributes-actions-container">
|
||||
<ng-template #headerTemplate>
|
||||
<div class="table-header-actions">
|
||||
<iqser-input-with-action
|
||||
[(value)]="searchService.searchValue"
|
||||
[placeholder]="'file-attributes-listing.search' | translate"
|
||||
@ -117,17 +60,79 @@
|
||||
(action)="fileInput.click()"
|
||||
*ngIf="currentUser.isAdmin"
|
||||
[tooltip]="'file-attributes-listing.upload-csv' | translate"
|
||||
[type]="circleButtonTypes.dark"
|
||||
icon="red:upload"
|
||||
tooltipPosition="above"
|
||||
[type]="circleButtonTypes.dark"
|
||||
></iqser-circle-button>
|
||||
|
||||
<iqser-icon-button
|
||||
(action)="openAddEditAttributeDialog($event)"
|
||||
*ngIf="currentUser.isAdmin"
|
||||
[label]="'file-attributes-listing.add-new' | translate"
|
||||
icon="red:plus"
|
||||
[type]="iconButtonTypes.primary"
|
||||
icon="red:plus"
|
||||
></iqser-icon-button>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #actionsTemplate let-attribute="entity">
|
||||
<div *ngIf="currentUser.isAdmin" class="action-buttons">
|
||||
<iqser-circle-button
|
||||
(action)="openAddEditAttributeDialog($event, attribute)"
|
||||
[tooltip]="'file-attributes-listing.action.edit' | translate"
|
||||
[type]="circleButtonTypes.dark"
|
||||
icon="iqser:edit"
|
||||
></iqser-circle-button>
|
||||
<iqser-circle-button
|
||||
(action)="openConfirmDeleteAttributeDialog($event, attribute)"
|
||||
[tooltip]="'file-attributes-listing.action.delete' | translate"
|
||||
[type]="circleButtonTypes.dark"
|
||||
icon="red:trash"
|
||||
></iqser-circle-button>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #labelTemplate let-attribute="entity">
|
||||
<div class="label cell">
|
||||
<span>{{ attribute.label }}</span>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #typeTemplate let-attribute="entity">
|
||||
<div [translate]="translations[attribute.type]" class="small-label cell"></div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #readonlyTemplate let-attribute="entity">
|
||||
<div class="center read-only cell">
|
||||
<mat-icon
|
||||
*ngIf="!attribute.editable"
|
||||
[matTooltip]="'file-attributes-listing.read-only' | translate"
|
||||
matTooltipPosition="above"
|
||||
svgIcon="red:read-only"
|
||||
></mat-icon>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #csvColumnHeaderTemplate let-attribute="entity">
|
||||
<div class="small-label cell">
|
||||
{{ attribute.csvColumnHeader }}
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #filterableTemplate let-attribute="entity">
|
||||
<div class="center cell">
|
||||
<iqser-round-checkbox *ngIf="attribute.filterable" [active]="true" [size]="18"></iqser-round-checkbox>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<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>
|
||||
</ng-template>
|
||||
|
||||
@ -1,38 +1,21 @@
|
||||
@import 'libs/common-ui/src/assets/styles/mixins';
|
||||
|
||||
.page-header .actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.content-container cdk-virtual-scroll-viewport {
|
||||
::ng-deep.cdk-virtual-scroll-content-wrapper {
|
||||
grid-template-columns: auto 2fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 11px;
|
||||
|
||||
.table-item {
|
||||
> div:not(.scrollbar-placeholder) {
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
> div {
|
||||
&.center {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&.label span {
|
||||
@include line-clamp(1);
|
||||
}
|
||||
|
||||
&.read-only mat-icon {
|
||||
width: 14px;
|
||||
height: 34px;
|
||||
}
|
||||
}
|
||||
}
|
||||
:host ::ng-deep iqser-table cdk-virtual-scroll-viewport .cdk-virtual-scroll-content-wrapper .table-item > div.cell {
|
||||
&.center {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&.has-scrollbar:hover ::ng-deep.cdk-virtual-scroll-content-wrapper {
|
||||
grid-template-columns: auto 2fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr;
|
||||
&.label span {
|
||||
@include line-clamp(1);
|
||||
}
|
||||
|
||||
&.read-only mat-icon {
|
||||
width: 14px;
|
||||
height: 34px;
|
||||
}
|
||||
|
||||
iqser-round-checkbox {
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
|
||||
@ -40,7 +23,3 @@
|
||||
display: none;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.table-item > div:not(.selection-column) iqser-round-checkbox {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
@ -1,4 +1,14 @@
|
||||
import { ChangeDetectionStrategy, Component, ElementRef, Injector, OnDestroy, OnInit, ViewChild } from '@angular/core';
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
ElementRef,
|
||||
forwardRef,
|
||||
Injector,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
TemplateRef,
|
||||
ViewChild
|
||||
} from '@angular/core';
|
||||
import { FileAttributeConfig, FileAttributesConfig, FileAttributesControllerService } from '@redaction/red-ui-http';
|
||||
import { AppStateService } from '@state/app-state.service';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
@ -19,7 +29,10 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
templateUrl: './file-attributes-listing-screen.component.html',
|
||||
styleUrls: ['./file-attributes-listing-screen.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
providers: [...DefaultListingServices]
|
||||
providers: [
|
||||
...DefaultListingServices,
|
||||
{ provide: ListingComponent, useExisting: forwardRef(() => FileAttributesListingScreenComponent) }
|
||||
]
|
||||
})
|
||||
export class FileAttributesListingScreenComponent extends ListingComponent<FileAttributeConfig> implements OnInit, OnDestroy {
|
||||
readonly iconButtonTypes = IconButtonTypes;
|
||||
@ -27,36 +40,14 @@ export class FileAttributesListingScreenComponent extends ListingComponent<FileA
|
||||
readonly currentUser = this._userService.currentUser;
|
||||
readonly translations = fileAttributeTypesTranslations;
|
||||
readonly tableHeaderLabel = _('file-attributes-listing.table-header.title');
|
||||
readonly tableColumnConfigs: TableColumnConfig<FileAttributeConfig>[] = [
|
||||
{
|
||||
label: _('file-attributes-listing.table-col-names.name'),
|
||||
sortByKey: 'label'
|
||||
},
|
||||
{
|
||||
label: _('file-attributes-listing.table-col-names.type'),
|
||||
sortByKey: 'type'
|
||||
},
|
||||
{
|
||||
label: _('file-attributes-listing.table-col-names.read-only'),
|
||||
sortByKey: 'editable',
|
||||
class: 'flex-center'
|
||||
},
|
||||
{ label: _('file-attributes-listing.table-col-names.csv-column') },
|
||||
{
|
||||
label: _('file-attributes-listing.table-col-names.filterable'),
|
||||
class: 'flex-center'
|
||||
},
|
||||
{
|
||||
label: _('file-attributes-listing.table-col-names.displayed-in-file-list'),
|
||||
class: 'flex-center'
|
||||
},
|
||||
{
|
||||
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')
|
||||
}
|
||||
];
|
||||
tableColumnConfigs: TableColumnConfig<FileAttributeConfig>[];
|
||||
@ViewChild('labelTemplate', { static: true }) labelTemplate: TemplateRef<never>;
|
||||
@ViewChild('typeTemplate', { static: true }) typeTemplate: TemplateRef<never>;
|
||||
@ViewChild('readonlyTemplate', { static: true }) readonlyTemplate: TemplateRef<never>;
|
||||
@ViewChild('csvColumnHeaderTemplate', { static: true }) csvColumnHeaderTemplate: TemplateRef<never>;
|
||||
@ViewChild('filterableTemplate', { static: true }) filterableTemplate: TemplateRef<never>;
|
||||
@ViewChild('displayedInFileListTemplate', { static: true }) displayedInFileListTemplate: TemplateRef<never>;
|
||||
@ViewChild('primaryAttributeTemplate', { static: true }) primaryAttributeTemplate: TemplateRef<never>;
|
||||
protected readonly _primaryKey = 'label';
|
||||
private _existingConfiguration: FileAttributesConfig;
|
||||
@ViewChild('fileInput') private _fileInput: ElementRef;
|
||||
@ -75,6 +66,7 @@ export class FileAttributesListingScreenComponent extends ListingComponent<FileA
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
this._configureTableColumns();
|
||||
await this._loadData();
|
||||
}
|
||||
|
||||
@ -130,6 +122,46 @@ export class FileAttributesListingScreenComponent extends ListingComponent<FileA
|
||||
);
|
||||
}
|
||||
|
||||
private _configureTableColumns() {
|
||||
this.tableColumnConfigs = [
|
||||
{
|
||||
label: _('file-attributes-listing.table-col-names.name'),
|
||||
sortByKey: 'label',
|
||||
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() {
|
||||
this._loadingService.start();
|
||||
|
||||
|
||||
@ -41,12 +41,6 @@
|
||||
<div translate="license-info-screen.end-user-license-text"></div>
|
||||
</div>
|
||||
|
||||
<!-- TODO-->
|
||||
<!-- <div class="row">-->
|
||||
<!-- <div translate="license-info-screen.3rd-party-title"></div>-->
|
||||
<!-- <div>Future feature: we will provide that with the /info endpoint</div>-->
|
||||
<!-- </div>-->
|
||||
|
||||
<div class="section-title all-caps-label" translate="license-info-screen.licensing-details"></div>
|
||||
|
||||
<div class="row">
|
||||
@ -95,12 +89,6 @@
|
||||
<div translate="license-info-screen.unlicensed-analyzed"></div>
|
||||
<div>{{ unlicensedInfo.numberOfAnalyzedPages }}</div>
|
||||
</div>
|
||||
|
||||
<!-- TODO-->
|
||||
<!-- <div class="row">-->
|
||||
<!-- <div>Progress bar</div>-->
|
||||
<!-- <div></div>-->
|
||||
<!-- </div>-->
|
||||
</div>
|
||||
|
||||
<combo-chart-component
|
||||
|
||||
@ -19,7 +19,7 @@
|
||||
|
||||
<redaction-admin-side-nav type="dossierTemplates"></redaction-admin-side-nav>
|
||||
|
||||
<div class="content-container" redactionHasScrollbar>
|
||||
<div class="content-container" iqserHasScrollbar>
|
||||
<div class="heading-xl" translate="reports-screen.title"></div>
|
||||
|
||||
<div class="description" translate="reports-screen.description"></div>
|
||||
@ -42,7 +42,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="right-container" redactionHasScrollbar>
|
||||
<div class="right-container" iqserHasScrollbar>
|
||||
<div class="header">
|
||||
<div class="heading" translate="reports-screen.report-documents"></div>
|
||||
<iqser-circle-button
|
||||
|
||||
@ -1,11 +1,6 @@
|
||||
@import '../../../../../assets/styles/variables';
|
||||
@import 'libs/common-ui/src/assets/styles/mixins';
|
||||
|
||||
.page-header .actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.content-container,
|
||||
.right-container {
|
||||
flex: 1;
|
||||
|
||||
@ -3,11 +3,6 @@
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.page-header .actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
ngx-monaco-editor {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
@ -9,106 +9,93 @@
|
||||
|
||||
<div class="red-content-inner">
|
||||
<div class="content-container">
|
||||
<iqser-table-header
|
||||
<iqser-table
|
||||
[bulkActions]="bulkActions"
|
||||
[itemSize]="80"
|
||||
[noDataText]="'trash.no-data.title' | translate"
|
||||
[noMatchText]="'trash.no-match.title' | translate"
|
||||
[selectionEnabled]="true"
|
||||
[tableColumnConfigs]="tableColumnConfigs"
|
||||
[tableHeaderLabel]="tableHeaderLabel"
|
||||
></iqser-table-header>
|
||||
|
||||
<redaction-empty-state
|
||||
*ngIf="entitiesService.noData$ | async"
|
||||
[text]="'trash.no-data.title' | translate"
|
||||
icon="red:template"
|
||||
></redaction-empty-state>
|
||||
|
||||
<redaction-empty-state *ngIf="noMatch$ | async" [text]="'trash.no-match.title' | translate"></redaction-empty-state>
|
||||
|
||||
<cdk-virtual-scroll-viewport [itemSize]="itemSize" redactionHasScrollbar>
|
||||
<div
|
||||
*cdkVirtualFor="let entity of sortedDisplayedEntities$ | async; trackBy: trackByPrimaryKey"
|
||||
class="table-item"
|
||||
[class.disabled]="!entity.canRestore"
|
||||
>
|
||||
<div (click)="toggleEntitySelected($event, entity)" class="selection-column">
|
||||
<iqser-round-checkbox [active]="isSelected(entity)"></iqser-round-checkbox>
|
||||
</div>
|
||||
|
||||
<div class="filename">
|
||||
<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 class="user-column">
|
||||
<redaction-initials-avatar [userId]="entity.ownerId" [withName]="true"></redaction-initials-avatar>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="small-label">
|
||||
{{ entity.softDeletedTime === '-' ? '-' : (entity.softDeletedTime | date: 'd MMM. yyyy, hh:mm a') }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<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>
|
||||
|
||||
<div class="scrollbar-placeholder"></div>
|
||||
</div>
|
||||
</cdk-virtual-scroll-viewport>
|
||||
[tableItemClasses]="{ disabled: disabledFn }"
|
||||
noDataIcon="red:template"
|
||||
></iqser-table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<ng-template #bulkActions>
|
||||
<div class="bulk-actions">
|
||||
<iqser-circle-button
|
||||
(action)="restore()"
|
||||
*ngIf="canRestoreSelected$ | async"
|
||||
[tooltip]="'trash.bulk.restore' | translate"
|
||||
[type]="circleButtonTypes.dark"
|
||||
icon="red:put-back"
|
||||
></iqser-circle-button>
|
||||
<iqser-circle-button
|
||||
(action)="restore()"
|
||||
*ngIf="canRestoreSelected$ | async"
|
||||
[tooltip]="'trash.bulk.restore' | translate"
|
||||
[type]="circleButtonTypes.dark"
|
||||
icon="red:put-back"
|
||||
></iqser-circle-button>
|
||||
|
||||
<iqser-circle-button
|
||||
(action)="hardDelete()"
|
||||
*ngIf="entitiesService.areSomeSelected$ | async"
|
||||
[tooltip]="'trash.bulk.delete' | translate"
|
||||
[type]="circleButtonTypes.dark"
|
||||
icon="red:trash"
|
||||
></iqser-circle-button>
|
||||
<iqser-circle-button
|
||||
(action)="hardDelete()"
|
||||
*ngIf="entitiesService.areSomeSelected$ | async"
|
||||
[tooltip]="'trash.bulk.delete' | translate"
|
||||
[type]="circleButtonTypes.dark"
|
||||
icon="red:trash"
|
||||
></iqser-circle-button>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #filenameTemplate let-entity="entity">
|
||||
<div class="cell filename">
|
||||
<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>
|
||||
</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>
|
||||
|
||||
@ -1,46 +0,0 @@
|
||||
@import '../../../../../assets/styles/variables';
|
||||
|
||||
.bulk-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
> *:not(:last-child) {
|
||||
margin-right: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.content-container {
|
||||
cdk-virtual-scroll-viewport {
|
||||
::ng-deep.cdk-virtual-scroll-content-wrapper {
|
||||
grid-template-columns: auto 1fr 1fr 1fr 1fr 11px;
|
||||
|
||||
.table-item {
|
||||
> div:not(.scrollbar-placeholder) {
|
||||
padding-left: 10px;
|
||||
|
||||
.table-item-title {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
> div {
|
||||
height: 80px;
|
||||
padding: 0 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.has-scrollbar:hover {
|
||||
::ng-deep.cdk-virtual-scroll-content-wrapper {
|
||||
grid-template-columns: auto 1fr 1fr 1fr 1fr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.disabled {
|
||||
> div {
|
||||
background-color: $grey-2;
|
||||
color: $grey-7;
|
||||
}
|
||||
}
|
||||
@ -1,13 +1,13 @@
|
||||
import { ChangeDetectionStrategy, Component, Injector, OnInit } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, Component, forwardRef, Injector, OnInit, TemplateRef, ViewChild } from '@angular/core';
|
||||
import { Dossier } from '@redaction/red-ui-http';
|
||||
import {
|
||||
CircleButtonTypes,
|
||||
DefaultListingServices,
|
||||
Listable,
|
||||
ListingComponent,
|
||||
LoadingService,
|
||||
Listable,
|
||||
TableColumnConfig,
|
||||
SortingOrders
|
||||
SortingOrders,
|
||||
TableColumnConfig
|
||||
} from '@iqser/common-ui';
|
||||
import { AppConfigKey, AppConfigService } from '@app-config/app-config.service';
|
||||
import * as moment from 'moment';
|
||||
@ -29,31 +29,21 @@ interface DossierListItem extends Dossier, Listable {
|
||||
templateUrl: './trash-screen.component.html',
|
||||
styleUrls: ['./trash-screen.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
providers: [...DefaultListingServices, DossiersService]
|
||||
providers: [
|
||||
...DefaultListingServices,
|
||||
DossiersService,
|
||||
{ provide: ListingComponent, useExisting: forwardRef(() => TrashScreenComponent) }
|
||||
]
|
||||
})
|
||||
export class TrashScreenComponent extends ListingComponent<DossierListItem> implements OnInit {
|
||||
readonly itemSize = 80;
|
||||
readonly circleButtonTypes = CircleButtonTypes;
|
||||
readonly tableHeaderLabel = _('trash.table-header.title');
|
||||
readonly canRestoreSelected$ = this._canRestoreSelected$;
|
||||
readonly tableColumnConfigs: readonly TableColumnConfig<DossierListItem>[] = [
|
||||
{
|
||||
label: _('trash.table-col-names.name'),
|
||||
sortByKey: 'dossierName'
|
||||
},
|
||||
{
|
||||
label: _('trash.table-col-names.owner'),
|
||||
class: 'user-column'
|
||||
},
|
||||
{
|
||||
label: _('trash.table-col-names.deleted-on'),
|
||||
sortByKey: 'softDeletedTime'
|
||||
},
|
||||
{
|
||||
label: _('trash.table-col-names.time-to-restore'),
|
||||
sortByKey: 'softDeletedTime'
|
||||
}
|
||||
];
|
||||
tableColumnConfigs: TableColumnConfig<DossierListItem>[];
|
||||
@ViewChild('filenameTemplate', { static: true }) filenameTemplate: TemplateRef<never>;
|
||||
@ViewChild('ownerTemplate', { static: true }) ownerTemplate: TemplateRef<never>;
|
||||
@ViewChild('deletedTimeTemplate', { static: true }) deletedTimeTemplate: TemplateRef<never>;
|
||||
@ViewChild('restoreDateTemplate', { static: true }) restoreDateTemplate: TemplateRef<never>;
|
||||
protected readonly _primaryKey = 'dossierName';
|
||||
private readonly _deleteRetentionHours = this._appConfigService.getConfig(AppConfigKey.DELETE_RETENTION_HOURS);
|
||||
|
||||
@ -75,7 +65,10 @@ export class TrashScreenComponent extends ListingComponent<DossierListItem> impl
|
||||
);
|
||||
}
|
||||
|
||||
disabledFn = (dossier: DossierListItem) => !dossier.canRestore;
|
||||
|
||||
async ngOnInit(): Promise<void> {
|
||||
this._configureTableColumns();
|
||||
this._loadingService.start();
|
||||
await this._loadDossiersData();
|
||||
this.sortingService.setSortingOption({
|
||||
@ -108,6 +101,31 @@ export class TrashScreenComponent extends ListingComponent<DossierListItem> impl
|
||||
this._loadingService.loadWhile(this._restore(dossiers));
|
||||
}
|
||||
|
||||
private _configureTableColumns() {
|
||||
this.tableColumnConfigs = [
|
||||
{
|
||||
label: _('trash.table-col-names.name'),
|
||||
sortByKey: 'dossierName',
|
||||
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 {
|
||||
return moment(softDeletedTime).add(this._deleteRetentionHours, 'hours').toISOString();
|
||||
}
|
||||
|
||||
@ -16,8 +16,8 @@
|
||||
(action)="openAddEditUserDialog($event)"
|
||||
*ngIf="currentUser.isUserAdmin"
|
||||
[label]="'user-listing.add-new' | translate"
|
||||
icon="red:plus"
|
||||
[type]="iconButtonTypes.primary"
|
||||
icon="red:plus"
|
||||
></iqser-icon-button>
|
||||
<iqser-circle-button
|
||||
*ngIf="currentUser.isUser"
|
||||
@ -32,58 +32,17 @@
|
||||
|
||||
<div class="red-content-inner">
|
||||
<div [class.extended]="collapsedDetails" class="content-container">
|
||||
<iqser-table-header
|
||||
<iqser-table
|
||||
[actionsTemplate]="actionsTemplate"
|
||||
[bulkActions]="bulkActions"
|
||||
[itemSize]="80"
|
||||
[noMatchText]="'user-listing.no-match.title' | translate"
|
||||
[selectionEnabled]="true"
|
||||
[hasEmptyColumn]="true"
|
||||
[tableColumnConfigs]="tableColumnConfigs"
|
||||
[tableHeaderLabel]="tableHeaderLabel"
|
||||
></iqser-table-header>
|
||||
|
||||
<redaction-empty-state *ngIf="noMatch$ | async" [text]="'user-listing.no-match.title' | translate"></redaction-empty-state>
|
||||
|
||||
<cdk-virtual-scroll-viewport [itemSize]="80" redactionHasScrollbar>
|
||||
<!-- Table lines -->
|
||||
<div *cdkVirtualFor="let user of sortedDisplayedEntities$ | async; trackBy: trackByPrimaryKey" class="table-item">
|
||||
<div (click)="toggleEntitySelected($event, user)" class="selection-column">
|
||||
<iqser-round-checkbox [active]="isSelected(user)"></iqser-round-checkbox>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<redaction-initials-avatar [showYou]="true" [userId]="user.id" [withName]="true"></redaction-initials-avatar>
|
||||
</div>
|
||||
<div class="small-label">{{ user.email || '-' }}</div>
|
||||
<div class="center">
|
||||
<mat-slide-toggle
|
||||
(toggleChange)="toggleActive(user)"
|
||||
[checked]="user.isActive"
|
||||
color="primary"
|
||||
></mat-slide-toggle>
|
||||
</div>
|
||||
<div class="small-label">{{ getDisplayRoles(user) }}</div>
|
||||
<div class="actions-container">
|
||||
<div class="action-buttons">
|
||||
<iqser-circle-button
|
||||
(action)="openAddEditUserDialog($event, user)"
|
||||
[tooltip]="'user-listing.action.edit' | translate"
|
||||
icon="iqser:edit"
|
||||
[type]="circleButtonTypes.dark"
|
||||
></iqser-circle-button>
|
||||
<iqser-circle-button
|
||||
(action)="openDeleteUsersDialog([user], $event)"
|
||||
[disabled]="user.id === userService.currentUser.id"
|
||||
[tooltip]="'user-listing.action.delete' | translate"
|
||||
icon="red:trash"
|
||||
[type]="circleButtonTypes.dark"
|
||||
></iqser-circle-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="scrollbar-placeholder"></div>
|
||||
</div>
|
||||
</cdk-virtual-scroll-viewport>
|
||||
emptyColumnWidth="1fr"
|
||||
></iqser-table>
|
||||
</div>
|
||||
|
||||
<div [class.collapsed]="collapsedDetails" class="right-container" redactionHasScrollbar>
|
||||
<div [class.collapsed]="collapsedDetails" class="right-container" iqserHasScrollbar>
|
||||
<redaction-users-stats
|
||||
(toggleCollapse)="collapsedDetails = !collapsedDetails"
|
||||
[chartData]="chartData"
|
||||
@ -101,8 +60,46 @@
|
||||
[tooltip]="
|
||||
(canDeleteSelected$ | async) ? ('user-listing.bulk.delete' | translate) : ('user-listing.bulk.delete-disabled' | translate)
|
||||
"
|
||||
[type]="circleButtonTypes.dark"
|
||||
icon="red:trash"
|
||||
tooltipPosition="after"
|
||||
[type]="circleButtonTypes.dark"
|
||||
></iqser-circle-button>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #nameTemplate let-user="entity">
|
||||
<div class="cell">
|
||||
<redaction-initials-avatar [showYou]="true" [userId]="user.id" [withName]="true"></redaction-initials-avatar>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #emailTemplate let-user="entity">
|
||||
<div class="small-label cell">{{ user.email || '-' }}</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #activeTemplate let-user="entity">
|
||||
<div class="center cell">
|
||||
<mat-slide-toggle (toggleChange)="toggleActive(user)" [checked]="user.isActive" color="primary"></mat-slide-toggle>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #rolesTemplate let-user="entity">
|
||||
<div class="small-label cell">{{ getDisplayRoles(user) }}</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #actionsTemplate let-user="entity">
|
||||
<div class="action-buttons">
|
||||
<iqser-circle-button
|
||||
(action)="openAddEditUserDialog($event, user)"
|
||||
[tooltip]="'user-listing.action.edit' | translate"
|
||||
[type]="circleButtonTypes.dark"
|
||||
icon="iqser:edit"
|
||||
></iqser-circle-button>
|
||||
<iqser-circle-button
|
||||
(action)="openDeleteUsersDialog([user], $event)"
|
||||
[disabled]="user.id === userService.currentUser.id"
|
||||
[tooltip]="'user-listing.action.delete' | translate"
|
||||
[type]="circleButtonTypes.dark"
|
||||
icon="red:trash"
|
||||
></iqser-circle-button>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
@ -1,23 +1,5 @@
|
||||
cdk-virtual-scroll-viewport {
|
||||
::ng-deep.cdk-virtual-scroll-content-wrapper {
|
||||
grid-template-columns: auto 2fr 1fr 1fr 1fr auto 11px;
|
||||
|
||||
.table-item {
|
||||
> div:not(.scrollbar-placeholder) {
|
||||
padding-left: 10px;
|
||||
|
||||
&.center {
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.has-scrollbar:hover {
|
||||
::ng-deep.cdk-virtual-scroll-content-wrapper {
|
||||
grid-template-columns: auto 2fr 1fr 1fr 1fr auto;
|
||||
}
|
||||
}
|
||||
:host ::ng-deep iqser-table cdk-virtual-scroll-viewport .cdk-virtual-scroll-content-wrapper .table-item > div.cell.center {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.right-container {
|
||||
@ -36,8 +18,6 @@ cdk-virtual-scroll-viewport {
|
||||
}
|
||||
|
||||
.page-header .actions {
|
||||
justify-content: flex-end;
|
||||
|
||||
iqser-input-with-action:not(:last-child) {
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Component, Injector, OnInit, QueryList, ViewChildren } from '@angular/core';
|
||||
import { Component, forwardRef, Injector, OnInit, QueryList, TemplateRef, ViewChild, ViewChildren } from '@angular/core';
|
||||
import { UserService, UserWrapper } from '@services/user.service';
|
||||
import { UserControllerService } from '@redaction/red-ui-http';
|
||||
import { AdminDialogService } from '../../services/admin-dialog.service';
|
||||
@ -22,7 +22,7 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
@Component({
|
||||
templateUrl: './user-listing-screen.component.html',
|
||||
styleUrls: ['./user-listing-screen.component.scss'],
|
||||
providers: [...DefaultListingServices]
|
||||
providers: [...DefaultListingServices, { provide: ListingComponent, useExisting: forwardRef(() => UserListingScreenComponent) }]
|
||||
})
|
||||
export class UserListingScreenComponent extends ListingComponent<UserWrapper> implements OnInit {
|
||||
readonly translations = rolesTranslations;
|
||||
@ -31,14 +31,13 @@ export class UserListingScreenComponent extends ListingComponent<UserWrapper> im
|
||||
readonly currentUser = this.userService.currentUser;
|
||||
readonly canDeleteSelected$ = this._canDeleteSelected$;
|
||||
readonly tableHeaderLabel = _('user-listing.table-header.title');
|
||||
readonly tableColumnConfigs: TableColumnConfig<UserWrapper>[] = [
|
||||
{ label: _('user-listing.table-col-names.name') },
|
||||
{ label: _('user-listing.table-col-names.email') },
|
||||
{ label: _('user-listing.table-col-names.active'), class: 'flex-center' },
|
||||
{ label: _('user-listing.table-col-names.roles') }
|
||||
];
|
||||
tableColumnConfigs: TableColumnConfig<UserWrapper>[];
|
||||
collapsedDetails = false;
|
||||
chartData: DoughnutChartConfig[] = [];
|
||||
@ViewChild('nameTemplate', { static: true }) nameTemplate: TemplateRef<never>;
|
||||
@ViewChild('emailTemplate', { static: true }) emailTemplate: TemplateRef<never>;
|
||||
@ViewChild('activeTemplate', { static: true }) activeTemplate: TemplateRef<never>;
|
||||
@ViewChild('rolesTemplate', { static: true }) rolesTemplate: TemplateRef<never>;
|
||||
protected readonly _primaryKey = 'id';
|
||||
@ViewChildren(InitialsAvatarComponent)
|
||||
private readonly _avatars: QueryList<InitialsAvatarComponent>;
|
||||
@ -61,6 +60,7 @@ export class UserListingScreenComponent extends ListingComponent<UserWrapper> im
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
this._configureTableColumns();
|
||||
await this._loadData();
|
||||
this.searchService.setSearchKey('searchKey');
|
||||
}
|
||||
@ -97,6 +97,15 @@ export class UserListingScreenComponent extends ListingComponent<UserWrapper> im
|
||||
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() {
|
||||
this.entitiesService.setEntities(await this.userService.loadAllUsers());
|
||||
await this.userService.loadAllUsers();
|
||||
|
||||
@ -26,14 +26,14 @@
|
||||
(action)="save()"
|
||||
[disabled]="configForm.invalid"
|
||||
[label]="'watermark-screen.action.save' | translate"
|
||||
icon="iqser:check"
|
||||
[type]="iconButtonTypes.primary"
|
||||
icon="iqser:check"
|
||||
></iqser-icon-button>
|
||||
<div (click)="revert()" class="all-caps-label cancel" translate="watermark-screen.action.revert"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="right-container" redactionHasScrollbar>
|
||||
<div class="right-container" iqserHasScrollbar>
|
||||
<div class="heading-xl" translate="watermark-screen.title"></div>
|
||||
<form (keyup)="configChanged()" [formGroup]="configForm">
|
||||
<div class="iqser-input-group w-300">
|
||||
@ -42,8 +42,8 @@
|
||||
[placeholder]="'watermark-screen.form.text-placeholder' | translate"
|
||||
class="w-full"
|
||||
formControlName="text"
|
||||
iqserHasScrollbar
|
||||
name="text"
|
||||
redactionHasScrollbar
|
||||
rows="4"
|
||||
type="text"
|
||||
></textarea>
|
||||
|
||||
@ -1,10 +1,5 @@
|
||||
@import '../../../../../assets/styles/variables';
|
||||
|
||||
.page-header .actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.content-container {
|
||||
order: 1;
|
||||
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="right-content" redactionHasScrollbar>
|
||||
<div class="right-content" iqserHasScrollbar>
|
||||
<div class="section">
|
||||
<div *ngFor="let attr of fileAttributesConfig?.fileAttributeConfigs" class="attribute">
|
||||
<div class="small-label">{{ attr.label }}:</div>
|
||||
|
||||
@ -61,8 +61,8 @@
|
||||
</div>
|
||||
<iqser-circle-button
|
||||
(action)="multiSelectActive = false"
|
||||
icon="iqser:close"
|
||||
[type]="circleButtonTypes.primary"
|
||||
icon="iqser:close"
|
||||
></iqser-circle-button>
|
||||
</div>
|
||||
|
||||
@ -134,11 +134,11 @@
|
||||
(keyup)="preventKeyDefault($event)"
|
||||
[class.active-panel]="!pagesPanelActive"
|
||||
class="annotations"
|
||||
redactionHasScrollbar
|
||||
iqserHasScrollbar
|
||||
tabindex="1"
|
||||
>
|
||||
<ng-container *ngIf="activeViewerPage && !displayedAnnotations.get(activeViewerPage)?.length">
|
||||
<redaction-empty-state
|
||||
<iqser-empty-state
|
||||
[horizontalPadding]="24"
|
||||
[text]="'file-preview.no-data.title' | translate"
|
||||
[verticalPadding]="40"
|
||||
@ -154,22 +154,22 @@
|
||||
</a
|
||||
>.
|
||||
</ng-container>
|
||||
</redaction-empty-state>
|
||||
</iqser-empty-state>
|
||||
<div class="no-annotations-buttons-container mt-32">
|
||||
<iqser-icon-button
|
||||
(action)="jumpToPreviousWithAnnotations()"
|
||||
[disabled]="activeViewerPage <= displayedPages[0]"
|
||||
[label]="'file-preview.tabs.annotations.jump-to-previous' | translate"
|
||||
icon="red:nav-prev"
|
||||
[type]="iconButtonTypes.dark"
|
||||
icon="red:nav-prev"
|
||||
></iqser-icon-button>
|
||||
<iqser-icon-button
|
||||
(action)="jumpToNextWithAnnotations()"
|
||||
[disabled]="activeViewerPage >= displayedPages[displayedPages.length - 1]"
|
||||
[label]="'file-preview.tabs.annotations.jump-to-next' | translate"
|
||||
[type]="iconButtonTypes.dark"
|
||||
class="mt-8"
|
||||
icon="red:nav-next"
|
||||
[type]="iconButtonTypes.dark"
|
||||
></iqser-icon-button>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
@ -11,6 +11,7 @@
|
||||
}
|
||||
|
||||
mat-icon {
|
||||
min-width: 16px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
fill-opacity: 0.6;
|
||||
|
||||
@ -13,7 +13,7 @@
|
||||
<div class="all-caps-label" translate="file-preview.tabs.exclude-pages.removed-from-redaction"></div>
|
||||
</div>
|
||||
|
||||
<div class="ranges" redactionHasScrollbar>
|
||||
<div class="ranges" iqserHasScrollbar>
|
||||
<div *ngFor="let range of excludedPagesRanges" class="range">
|
||||
<ng-container *ngIf="range.startPage === range.endPage">
|
||||
{{ range.startPage }}
|
||||
|
||||
@ -1,7 +0,0 @@
|
||||
<button (click)="scroll(buttonType.top)" [hidden]="(showScrollUp$ | async) === false" class="scroll-button top pointer">
|
||||
<mat-icon svgIcon="red:arrow-down-o"></mat-icon>
|
||||
</button>
|
||||
|
||||
<button (click)="scroll(buttonType.bottom)" [hidden]="(showScrollDown$ | async) === false" class="scroll-button bottom pointer">
|
||||
<mat-icon svgIcon="red:arrow-down-o"></mat-icon>
|
||||
</button>
|
||||
@ -1,30 +0,0 @@
|
||||
@import '../../../../../assets/styles/variables';
|
||||
|
||||
.scroll-button {
|
||||
background-color: $white;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
height: 40px;
|
||||
width: 44px;
|
||||
border: none;
|
||||
border-radius: 8px 0 0 8px;
|
||||
box-shadow: -1px 1px 5px 0 rgba(40, 50, 65, 0.25);
|
||||
|
||||
&.bottom {
|
||||
bottom: 30px;
|
||||
}
|
||||
|
||||
&.top {
|
||||
top: 100px;
|
||||
|
||||
mat-icon {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mat-icon {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
color: $grey-7;
|
||||
}
|
||||
@ -1,60 +0,0 @@
|
||||
import { Component, HostListener, Input, OnInit } from '@angular/core';
|
||||
import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
|
||||
import { concatMap, delay, distinctUntilChanged, map, startWith } from 'rxjs/operators';
|
||||
import { Observable, of } from 'rxjs';
|
||||
|
||||
const ButtonTypes = {
|
||||
top: 'top',
|
||||
bottom: 'bottom'
|
||||
} as const;
|
||||
|
||||
type ButtonType = keyof typeof ButtonTypes;
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-scroll-button',
|
||||
templateUrl: './scroll-button.component.html',
|
||||
styleUrls: ['./scroll-button.component.scss']
|
||||
})
|
||||
export class ScrollButtonComponent implements OnInit {
|
||||
readonly buttonType = ButtonTypes;
|
||||
|
||||
@Input()
|
||||
scrollViewport: CdkVirtualScrollViewport;
|
||||
@Input()
|
||||
itemSize: number;
|
||||
|
||||
showScrollUp$: Observable<boolean>;
|
||||
showScrollDown$: Observable<boolean>;
|
||||
|
||||
ngOnInit() {
|
||||
const scrollSize = () => this.scrollViewport.getDataLength() * this.itemSize;
|
||||
const scrollIsNeeded = () => this.scrollViewport.getViewportSize() < scrollSize();
|
||||
const reachedEnd = (type: ButtonType) => this.scrollViewport.measureScrollOffset(type) === 0;
|
||||
|
||||
const showScrollUp = () => scrollIsNeeded() && !reachedEnd(ButtonTypes.top);
|
||||
const showScrollDown = () => scrollIsNeeded() && !reachedEnd(ButtonTypes.bottom);
|
||||
|
||||
const scroll$ = this.scrollViewport.elementScrolled().pipe(
|
||||
startWith(''),
|
||||
/** Delay first value so that we can wait for items to be rendered in viewport and get correct values */
|
||||
concatMap((value, index) => (index === 0 ? of(value).pipe(delay(0)) : of(value)))
|
||||
);
|
||||
this.showScrollUp$ = scroll$.pipe(map(showScrollUp), distinctUntilChanged());
|
||||
this.showScrollDown$ = scroll$.pipe(map(showScrollDown), distinctUntilChanged());
|
||||
}
|
||||
|
||||
scroll(type: ButtonType): void {
|
||||
const viewportSize = (this.scrollViewport?.getViewportSize() - this.itemSize) * (type === ButtonTypes.top ? -1 : 1);
|
||||
const scrollOffset = this.scrollViewport?.measureScrollOffset('top');
|
||||
this.scrollViewport?.scrollToOffset(scrollOffset + viewportSize, 'smooth');
|
||||
}
|
||||
|
||||
@HostListener('document:keyup', ['$event'])
|
||||
spaceAndPageDownScroll(event: KeyboardEvent): void {
|
||||
if (['Space', 'PageDown'].includes(event.code) && (event.target as any).tagName === 'BODY') {
|
||||
this.scroll(ButtonTypes.bottom);
|
||||
} else if (['PageUp'].includes(event.code) && (event.target as any).tagName === 'BODY') {
|
||||
this.scroll(ButtonTypes.top);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -34,8 +34,8 @@
|
||||
<textarea
|
||||
[placeholder]="'add-dossier-dialog.form.description.placeholder' | translate"
|
||||
formControlName="description"
|
||||
iqserHasScrollbar
|
||||
name="description"
|
||||
redactionHasScrollbar
|
||||
rows="5"
|
||||
type="text"
|
||||
></textarea>
|
||||
@ -63,8 +63,8 @@
|
||||
<div class="d-flex">
|
||||
<redaction-select
|
||||
[label]="'report-type.label' | translate: { length: reportTemplateIdsLength }"
|
||||
[options]="availableReportTypes"
|
||||
[optionTemplate]="reportTemplateOptionTemplate"
|
||||
[options]="availableReportTypes"
|
||||
[valueMapper]="reportTemplateValueMapper"
|
||||
class="mr-16"
|
||||
formControlName="reportTemplateIds"
|
||||
@ -86,8 +86,8 @@
|
||||
(action)="saveDossierAndAddMembers()"
|
||||
[disabled]="disabled"
|
||||
[label]="'add-dossier-dialog.actions.save-and-add-members' | translate"
|
||||
icon="red:assign"
|
||||
[type]="iconButtonTypes.dark"
|
||||
icon="red:assign"
|
||||
></iqser-icon-button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@ -23,7 +23,7 @@
|
||||
|
||||
<div [class.required]="!isDocumentAdmin" class="iqser-input-group w-300">
|
||||
<label translate="change-legal-basis-dialog.content.comment"></label>
|
||||
<textarea formControlName="comment" name="comment" redactionHasScrollbar rows="4" type="text"></textarea>
|
||||
<textarea formControlName="comment" iqserHasScrollbar name="comment" rows="4" type="text"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@ -4,11 +4,11 @@
|
||||
{{ 'edit-dossier-dialog.attributes.custom-attributes' | translate }}
|
||||
</div>
|
||||
|
||||
<redaction-empty-state
|
||||
<iqser-empty-state
|
||||
*ngIf="!customAttributes.length"
|
||||
[text]="'edit-dossier-dialog.attributes.no-custom-attributes' | translate"
|
||||
icon="red:attribute"
|
||||
></redaction-empty-state>
|
||||
></iqser-empty-state>
|
||||
|
||||
<div *ngFor="let attr of customAttributes" [class.datepicker-wrapper]="isDate(attr)" class="iqser-input-group">
|
||||
<label>{{ attr.label }}</label>
|
||||
@ -34,11 +34,11 @@
|
||||
{{ 'edit-dossier-dialog.attributes.image-attributes' | translate }}
|
||||
</div>
|
||||
|
||||
<redaction-empty-state
|
||||
<iqser-empty-state
|
||||
*ngIf="!imageAttributes.length"
|
||||
[text]="'edit-dossier-dialog.attributes.no-image-attributes' | translate"
|
||||
icon="red:attribute"
|
||||
></redaction-empty-state>
|
||||
></iqser-empty-state>
|
||||
|
||||
<div
|
||||
*ngFor="let attr of imageAttributes"
|
||||
|
||||
@ -1,76 +1,75 @@
|
||||
<iqser-table-header
|
||||
<iqser-table
|
||||
[bulkActions]="bulkActions"
|
||||
[headerTemplate]="headerTemplate"
|
||||
[itemSize]="50"
|
||||
[noDataText]="'edit-dossier-dialog.deleted-documents.no-data.title' | translate"
|
||||
[selectionEnabled]="true"
|
||||
[tableColumnConfigs]="tableColumnConfigs"
|
||||
[tableHeaderLabel]="tableHeaderLabel"
|
||||
>
|
||||
noDataIcon="red:document"
|
||||
></iqser-table>
|
||||
|
||||
<ng-template #headerTemplate>
|
||||
<div
|
||||
[translateParams]="{ hours: deleteRetentionHours }"
|
||||
[translate]="'edit-dossier-dialog.deleted-documents.instructions'"
|
||||
class="instructions"
|
||||
></div>
|
||||
</iqser-table-header>
|
||||
|
||||
<redaction-empty-state
|
||||
*ngIf="entitiesService.noData$ | async"
|
||||
[text]="'edit-dossier-dialog.deleted-documents.no-data.title' | translate"
|
||||
icon="red:document"
|
||||
></redaction-empty-state>
|
||||
|
||||
<cdk-virtual-scroll-viewport *ngIf="(entitiesService.noData$ | async) === false" [itemSize]="itemSize" redactionHasScrollbar>
|
||||
<div *cdkVirtualFor="let file of sortedDisplayedEntities$ | async; trackBy: trackByPrimaryKey" class="table-item">
|
||||
<div (click)="toggleEntitySelected($event, file)" class="selection-column">
|
||||
<iqser-round-checkbox [active]="isSelected(file)"></iqser-round-checkbox>
|
||||
</div>
|
||||
<div class="filename">
|
||||
<span>{{ file.filename }}</span>
|
||||
</div>
|
||||
<div class="small-label stats-subtitle">
|
||||
<div>
|
||||
<mat-icon svgIcon="red:pages"></mat-icon>
|
||||
{{ file.numberOfPages }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="small-label">{{ file.softDeleted | date: 'exactDate' }}</div>
|
||||
<div>
|
||||
<div class="small-label">{{ file.restoreDate | date: 'timeFromNow' }}</div>
|
||||
<div class="action-buttons">
|
||||
<iqser-circle-button
|
||||
(action)="restore([file])"
|
||||
*ngIf="file.canRestore"
|
||||
[tooltip]="'edit-dossier-dialog.deleted-documents.action.restore' | translate"
|
||||
[type]="circleButtonTypes.dark"
|
||||
icon="red:put-back"
|
||||
></iqser-circle-button>
|
||||
|
||||
<iqser-circle-button
|
||||
(action)="hardDelete([file])"
|
||||
[tooltip]="'edit-dossier-dialog.deleted-documents.action.delete' | translate"
|
||||
[type]="circleButtonTypes.dark"
|
||||
icon="red:trash"
|
||||
></iqser-circle-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="scrollbar-placeholder"></div>
|
||||
</div>
|
||||
</cdk-virtual-scroll-viewport>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #bulkActions>
|
||||
<div class="bulk-actions">
|
||||
<iqser-circle-button
|
||||
(action)="restore()"
|
||||
*ngIf="canRestoreSelected$ | async"
|
||||
[tooltip]="'edit-dossier-dialog.deleted-documents.bulk.restore' | translate"
|
||||
[type]="circleButtonTypes.dark"
|
||||
icon="red:put-back"
|
||||
></iqser-circle-button>
|
||||
<iqser-circle-button
|
||||
(action)="restore()"
|
||||
*ngIf="canRestoreSelected$ | async"
|
||||
[tooltip]="'edit-dossier-dialog.deleted-documents.bulk.restore' | translate"
|
||||
[type]="circleButtonTypes.dark"
|
||||
icon="red:put-back"
|
||||
></iqser-circle-button>
|
||||
|
||||
<iqser-circle-button
|
||||
(action)="hardDelete()"
|
||||
*ngIf="entitiesService.areSomeSelected$ | async"
|
||||
[tooltip]="'edit-dossier-dialog.deleted-documents.bulk.delete' | translate"
|
||||
[type]="circleButtonTypes.dark"
|
||||
icon="red:trash"
|
||||
></iqser-circle-button>
|
||||
<iqser-circle-button
|
||||
(action)="hardDelete()"
|
||||
*ngIf="entitiesService.areSomeSelected$ | async"
|
||||
[tooltip]="'edit-dossier-dialog.deleted-documents.bulk.delete' | translate"
|
||||
[type]="circleButtonTypes.dark"
|
||||
icon="red:trash"
|
||||
></iqser-circle-button>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #filenameTemplate let-file="entity">
|
||||
<div class="cell filename">
|
||||
<span>{{ file.filename }}</span>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #pagesTemplate let-file="entity">
|
||||
<div class="cell small-label stats-subtitle">
|
||||
<div>
|
||||
<mat-icon svgIcon="red:pages"></mat-icon>
|
||||
{{ file.numberOfPages }}
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #deletedDateTemplate let-file="entity">
|
||||
<div class="cell small-label">{{ file.softDeleted | date: 'exactDate' }}</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #restoreDateTemplate let-file="entity">
|
||||
<div class="cell">
|
||||
<div class="small-label">{{ file.restoreDate | date: 'timeFromNow' }}</div>
|
||||
<div class="action-buttons">
|
||||
<iqser-circle-button
|
||||
(action)="restore([file])"
|
||||
*ngIf="file.canRestore"
|
||||
[tooltip]="'edit-dossier-dialog.deleted-documents.action.restore' | translate"
|
||||
[type]="circleButtonTypes.dark"
|
||||
icon="red:put-back"
|
||||
></iqser-circle-button>
|
||||
|
||||
<iqser-circle-button
|
||||
(action)="hardDelete([file])"
|
||||
[tooltip]="'edit-dossier-dialog.deleted-documents.action.delete' | translate"
|
||||
[type]="circleButtonTypes.dark"
|
||||
icon="red:trash"
|
||||
></iqser-circle-button>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
@ -7,35 +7,10 @@
|
||||
text-align: end;
|
||||
}
|
||||
|
||||
cdk-virtual-scroll-viewport {
|
||||
height: calc(100% - 81px);
|
||||
:host ::ng-deep iqser-table cdk-virtual-scroll-viewport {
|
||||
height: calc(100% - 81px) !important;
|
||||
|
||||
::ng-deep.cdk-virtual-scroll-content-wrapper {
|
||||
grid-template-columns: auto 3fr 1fr 2fr 2fr 11px;
|
||||
|
||||
.table-item > div {
|
||||
height: 50px;
|
||||
|
||||
&.filename span {
|
||||
@include line-clamp(1);
|
||||
}
|
||||
|
||||
&.stats-subtitle > div {
|
||||
width: fit-content;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.has-scrollbar:hover ::ng-deep.cdk-virtual-scroll-content-wrapper {
|
||||
grid-template-columns: auto 3fr 1fr 2fr 2fr;
|
||||
}
|
||||
}
|
||||
|
||||
.bulk-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
> *:not(:last-child) {
|
||||
margin-right: 2px;
|
||||
.cdk-virtual-scroll-content-wrapper .table-item > div.cell.filename span {
|
||||
@include line-clamp(1);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { Component, EventEmitter, Injector, Input, OnInit, Output } from '@angular/core';
|
||||
import { Component, EventEmitter, forwardRef, Injector, Input, OnInit, Output, TemplateRef, ViewChild } from '@angular/core';
|
||||
import { EditDossierSectionInterface } from '../edit-dossier-section.interface';
|
||||
import { DossierWrapper } from '@state/model/dossier.wrapper';
|
||||
import { CircleButtonTypes, DefaultListingServices, ListingComponent, Listable, LoadingService, TableColumnConfig } from '@iqser/common-ui';
|
||||
import { CircleButtonTypes, DefaultListingServices, Listable, ListingComponent, LoadingService, TableColumnConfig } from '@iqser/common-ui';
|
||||
import { FileManagementControllerService, FileStatus, StatusControllerService } from '@redaction/red-ui-http';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import * as moment from 'moment';
|
||||
@ -22,7 +22,10 @@ interface FileListItem extends FileStatus, Listable {
|
||||
selector: 'redaction-edit-dossier-deleted-documents',
|
||||
templateUrl: './edit-dossier-deleted-documents.component.html',
|
||||
styleUrls: ['./edit-dossier-deleted-documents.component.scss'],
|
||||
providers: [...DefaultListingServices]
|
||||
providers: [
|
||||
...DefaultListingServices,
|
||||
{ provide: ListingComponent, useExisting: forwardRef(() => EditDossierDeletedDocumentsComponent) }
|
||||
]
|
||||
})
|
||||
export class EditDossierDeletedDocumentsComponent extends ListingComponent<FileListItem> implements EditDossierSectionInterface, OnInit {
|
||||
@Input() dossierWrapper: DossierWrapper;
|
||||
@ -30,16 +33,14 @@ export class EditDossierDeletedDocumentsComponent extends ListingComponent<FileL
|
||||
readonly changed = false;
|
||||
readonly canRestoreSelected$ = this._canRestoreSelected$;
|
||||
disabled: boolean;
|
||||
readonly tableColumnConfigs: TableColumnConfig<FileListItem>[] = [
|
||||
{ label: _('edit-dossier-dialog.deleted-documents.table-col-names.name') },
|
||||
{ label: _('edit-dossier-dialog.deleted-documents.table-col-names.pages') },
|
||||
{ label: _('edit-dossier-dialog.deleted-documents.table-col-names.deleted-on') },
|
||||
{ label: _('edit-dossier-dialog.deleted-documents.table-col-names.time-to-restore') }
|
||||
];
|
||||
tableColumnConfigs: TableColumnConfig<FileListItem>[];
|
||||
readonly tableHeaderLabel = _('edit-dossier-dialog.deleted-documents.table-header.label');
|
||||
readonly itemSize = 50;
|
||||
readonly circleButtonTypes = CircleButtonTypes;
|
||||
readonly deleteRetentionHours = this._appConfigService.getConfig(AppConfigKey.DELETE_RETENTION_HOURS);
|
||||
@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>;
|
||||
protected readonly _primaryKey = 'fileId';
|
||||
|
||||
constructor(
|
||||
@ -80,6 +81,7 @@ export class EditDossierDeletedDocumentsComponent extends ListingComponent<FileL
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
this._configureTableColumns();
|
||||
this._loadingService.start();
|
||||
const files = await this._statusController.getDeletedFileStatus(this.dossierWrapper.dossierId).toPromise();
|
||||
this.entitiesService.setEntities(this._toListItems(files));
|
||||
@ -94,6 +96,30 @@ export class EditDossierDeletedDocumentsComponent extends ListingComponent<FileL
|
||||
this._loadingService.loadWhile(this._restore(files));
|
||||
}
|
||||
|
||||
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,
|
||||
width: '2fr'
|
||||
},
|
||||
{
|
||||
label: _('edit-dossier-dialog.deleted-documents.table-col-names.time-to-restore'),
|
||||
template: this.restoreDateTemplate,
|
||||
width: '2fr'
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
private async _restore(files: FileListItem[]): Promise<void> {
|
||||
const fileIds = files.map(f => f.fileId);
|
||||
await this._fileManagementController.restoreFiles(fileIds, this.dossierWrapper.dossierId).toPromise();
|
||||
|
||||
@ -30,8 +30,8 @@
|
||||
<textarea
|
||||
[placeholder]="'edit-dossier-dialog.general-info.form.description.placeholder' | translate"
|
||||
formControlName="description"
|
||||
iqserHasScrollbar
|
||||
name="description"
|
||||
redactionHasScrollbar
|
||||
rows="5"
|
||||
type="text"
|
||||
></textarea>
|
||||
@ -60,8 +60,8 @@
|
||||
(action)="deleteDossier()"
|
||||
*ngIf="permissionsService.canDeleteDossier(dossierWrapper)"
|
||||
[label]="'dossier-listing.delete.action' | translate"
|
||||
icon="red:trash"
|
||||
[type]="iconButtonTypes.dark"
|
||||
icon="red:trash"
|
||||
></iqser-icon-button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@ -23,7 +23,7 @@
|
||||
|
||||
<div [class.required]="!isDocumentAdmin" class="iqser-input-group w-300">
|
||||
<label translate="manual-annotation.dialog.content.comment"></label>
|
||||
<textarea formControlName="comment" name="comment" redactionHasScrollbar rows="4" type="text"></textarea>
|
||||
<textarea formControlName="comment" iqserHasScrollbar name="comment" rows="4" type="text"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@ -41,7 +41,7 @@
|
||||
|
||||
<div [class.required]="!isDocumentAdmin" class="iqser-input-group w-300">
|
||||
<label translate="manual-annotation.dialog.content.comment"></label>
|
||||
<textarea formControlName="comment" name="comment" redactionHasScrollbar rows="4" type="text"></textarea>
|
||||
<textarea formControlName="comment" iqserHasScrollbar name="comment" rows="4" type="text"></textarea>
|
||||
</div>
|
||||
|
||||
<div *ngIf="isDictionaryRequest && !isFalsePositiveRequest" class="iqser-input-group required w-300">
|
||||
|
||||
@ -18,7 +18,7 @@
|
||||
|
||||
<div [class.required]="!isDocumentAdmin" class="iqser-input-group w-300">
|
||||
<label translate="recategorize-image-dialog.content.comment"></label>
|
||||
<textarea formControlName="comment" name="comment" redactionHasScrollbar rows="4" type="text"></textarea>
|
||||
<textarea formControlName="comment" iqserHasScrollbar name="comment" rows="4" type="text"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@ -41,12 +41,12 @@
|
||||
|
||||
<div [class.required]="!permissionsService.isApprover()" class="iqser-input-group w-300">
|
||||
<label translate="manual-annotation.dialog.content.comment"></label>
|
||||
<textarea formControlName="comment" name="comment" redactionHasScrollbar rows="4" type="text"></textarea>
|
||||
<textarea formControlName="comment" iqserHasScrollbar name="comment" rows="4" type="text"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dialog-actions">
|
||||
<button color="primary" mat-flat-button type="submit" [disabled]="!redactionForm.valid">
|
||||
<button [disabled]="!redactionForm.valid" color="primary" mat-flat-button type="submit">
|
||||
{{ 'remove-annotations-dialog.confirm' | translate }}
|
||||
</button>
|
||||
<button (click)="deny()" color="primary" mat-flat-button>
|
||||
|
||||
@ -41,7 +41,6 @@ import { UserPreferenceControllerService } from '@redaction/red-ui-http';
|
||||
import { EditDossierDictionaryComponent } from './dialogs/edit-dossier-dialog/dictionary/edit-dossier-dictionary.component';
|
||||
import { EditDossierTeamMembersComponent } from './dialogs/edit-dossier-dialog/team-members/edit-dossier-team-members.component';
|
||||
import { TeamMembersManagerComponent } from './components/team-members-manager/team-members-manager.component';
|
||||
import { ScrollButtonComponent } from './components/scroll-button/scroll-button.component';
|
||||
import { ChangeLegalBasisDialogComponent } from './dialogs/change-legal-basis-dialog/change-legal-basis-dialog.component';
|
||||
import { PageExclusionComponent } from './components/page-exclusion/page-exclusion.component';
|
||||
import { RecategorizeImageDialogComponent } from './dialogs/recategorize-image-dialog/recategorize-image-dialog.component';
|
||||
@ -87,7 +86,6 @@ const components = [
|
||||
EditDossierTeamMembersComponent,
|
||||
EditDossierAttributesComponent,
|
||||
TeamMembersManagerComponent,
|
||||
ScrollButtonComponent,
|
||||
PageExclusionComponent,
|
||||
DossierDetailsStatsComponent,
|
||||
EditDossierDeletedDocumentsComponent,
|
||||
|
||||
@ -5,79 +5,19 @@
|
||||
|
||||
<div class="red-content-inner">
|
||||
<div class="content-container">
|
||||
<iqser-table-header [tableColumnConfigs]="tableColumnConfigs" [tableHeaderLabel]="tableHeaderLabel"></iqser-table-header>
|
||||
|
||||
<redaction-empty-state
|
||||
(action)="openAddDossierDialog()"
|
||||
*ngIf="entitiesService.noData$ | async"
|
||||
[buttonLabel]="'dossier-listing.no-data.action' | translate"
|
||||
[showButton]="currentUser.isManager"
|
||||
[text]="'dossier-listing.no-data.title' | translate"
|
||||
icon="red:folder"
|
||||
></redaction-empty-state>
|
||||
|
||||
<redaction-empty-state *ngIf="noMatch$ | async" [text]="'dossier-listing.no-match.title' | translate"></redaction-empty-state>
|
||||
|
||||
<cdk-virtual-scroll-viewport #scrollViewport [itemSize]="itemSize" redactionHasScrollbar>
|
||||
<div
|
||||
*cdkVirtualFor="let dossier of sortedDisplayedEntities$ | async; trackBy: trackByPrimaryKey"
|
||||
[class.pointer]="!!dossier"
|
||||
[routerLink]="['/main/dossiers/' + dossier.dossierId.toString()]"
|
||||
class="table-item"
|
||||
>
|
||||
<div class="filename">
|
||||
<div [matTooltip]="dossier.dossierName" class="table-item-title heading" matTooltipPosition="above">
|
||||
{{ dossier.dossierName }}
|
||||
</div>
|
||||
<div class="small-label stats-subtitle">
|
||||
<div>
|
||||
<mat-icon svgIcon="red:template"></mat-icon>
|
||||
{{ dossier.dossierTemplateName }}
|
||||
</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.memberCount }}
|
||||
</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>
|
||||
<div>
|
||||
<redaction-needs-work-badge [needsWorkInput]="dossier"></redaction-needs-work-badge>
|
||||
</div>
|
||||
<div class="user-column">
|
||||
<redaction-initials-avatar [userId]="dossier.ownerId" [withName]="true"></redaction-initials-avatar>
|
||||
</div>
|
||||
<div class="status-container">
|
||||
<redaction-dossier-listing-actions
|
||||
(actionPerformed)="calculateData()"
|
||||
[dossier]="dossier"
|
||||
></redaction-dossier-listing-actions>
|
||||
</div>
|
||||
<div class="scrollbar-placeholder"></div>
|
||||
</div>
|
||||
</cdk-virtual-scroll-viewport>
|
||||
|
||||
<redaction-scroll-button [itemSize]="itemSize" [scrollViewport]="scrollViewport"></redaction-scroll-button>
|
||||
<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" redactionHasScrollbar>
|
||||
<div class="right-container" iqserHasScrollbar>
|
||||
<redaction-dossier-listing-details
|
||||
*ngIf="(entitiesService.noData$ | async) === false"
|
||||
[documentsChartData]="documentsChartData"
|
||||
@ -87,6 +27,60 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<ng-template #needsWorkTemplate let-filter="filter">
|
||||
<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" matTooltipPosition="above">
|
||||
{{ dossier.dossierName }}
|
||||
</div>
|
||||
<div class="small-label stats-subtitle">
|
||||
<div>
|
||||
<mat-icon svgIcon="red:template"></mat-icon>
|
||||
{{ dossier.dossierTemplateName }}
|
||||
</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.memberCount }}
|
||||
</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,44 +1,22 @@
|
||||
@import '../../../../../assets/styles/variables';
|
||||
|
||||
.content-container {
|
||||
position: relative;
|
||||
|
||||
cdk-virtual-scroll-viewport {
|
||||
::ng-deep.cdk-virtual-scroll-content-wrapper {
|
||||
grid-template-columns: 2fr 1fr 1fr auto 11px;
|
||||
|
||||
.table-item {
|
||||
> div {
|
||||
height: 85px;
|
||||
padding: 0 24px;
|
||||
}
|
||||
|
||||
.status-container {
|
||||
width: 160px;
|
||||
padding-right: 13px;
|
||||
}
|
||||
}
|
||||
: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 {
|
||||
::ng-deep.cdk-virtual-scroll-content-wrapper {
|
||||
grid-template-columns: 2fr 1fr 1fr auto;
|
||||
}
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
redaction-dossier-listing-details {
|
||||
min-width: 466px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.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,4 +1,4 @@
|
||||
import { AfterViewInit, Component, Injector, OnDestroy, OnInit, TemplateRef, ViewChild } from '@angular/core';
|
||||
import { AfterViewInit, Component, forwardRef, Injector, OnDestroy, OnInit, TemplateRef, ViewChild } from '@angular/core';
|
||||
import { Dossier } from '@redaction/red-ui-http';
|
||||
import { AppStateService } from '@state/app-state.service';
|
||||
import { UserService } from '@services/user.service';
|
||||
@ -16,25 +16,22 @@ 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 } from '@iqser/common-ui';
|
||||
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';
|
||||
import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
|
||||
|
||||
@Component({
|
||||
templateUrl: './dossier-listing-screen.component.html',
|
||||
styleUrls: ['./dossier-listing-screen.component.scss'],
|
||||
providers: [...DefaultListingServices]
|
||||
providers: [...DefaultListingServices, { provide: ListingComponent, useExisting: forwardRef(() => DossierListingScreenComponent) }]
|
||||
})
|
||||
export class DossierListingScreenComponent
|
||||
extends ListingComponent<DossierWrapper>
|
||||
implements OnInit, AfterViewInit, OnDestroy, OnAttach, OnDetach
|
||||
{
|
||||
readonly itemSize = 85;
|
||||
protected readonly _primaryKey = 'dossierName';
|
||||
readonly currentUser = this._userService.currentUser;
|
||||
readonly tableHeaderLabel = _('dossier-listing.table-header.title');
|
||||
readonly buttonConfigs: readonly ButtonConfig[] = [
|
||||
@ -46,32 +43,21 @@ export class DossierListingScreenComponent
|
||||
type: 'primary'
|
||||
}
|
||||
];
|
||||
readonly tableColumnConfigs: readonly TableColumnConfig<DossierWrapper>[] = [
|
||||
{
|
||||
label: _('dossier-listing.table-col-names.name'),
|
||||
sortByKey: 'dossierName'
|
||||
},
|
||||
{
|
||||
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'
|
||||
}
|
||||
];
|
||||
|
||||
tableColumnConfigs: TableColumnConfig<DossierWrapper>[];
|
||||
dossiersChartData: DoughnutChartConfig[] = [];
|
||||
documentsChartData: DoughnutChartConfig[] = [];
|
||||
@ViewChild('nameTemplate', { static: true }) nameTemplate: TemplateRef<never>;
|
||||
@ViewChild('needsWorkTemplate', { static: true }) needsWorkTemplate: TemplateRef<never>;
|
||||
@ViewChild('ownerTemplate', { static: true }) ownerTemplate: TemplateRef<never>;
|
||||
@ViewChild('statusTemplate', { static: true }) statusTemplate: TemplateRef<never>;
|
||||
protected readonly _primaryKey = 'dossierName';
|
||||
private _lastScrolledIndex: number;
|
||||
|
||||
@ViewChild('needsWorkTemplate', { read: TemplateRef, static: true })
|
||||
private readonly _needsWorkTemplate: TemplateRef<unknown>;
|
||||
@ViewChild(CdkVirtualScrollViewport)
|
||||
private readonly _scrollViewport: CdkVirtualScrollViewport;
|
||||
@ViewChild('needsWorkFilterTemplate', {
|
||||
read: TemplateRef,
|
||||
static: true
|
||||
})
|
||||
private readonly _needsWorkFilterTemplate: TemplateRef<unknown>;
|
||||
@ViewChild(TableComponent) private readonly _tableComponent: TableComponent<DossierWrapper>;
|
||||
|
||||
constructor(
|
||||
private readonly _router: Router,
|
||||
@ -97,7 +83,10 @@ export class DossierListingScreenComponent
|
||||
return this.entitiesService.all.length - this._activeDossiersCount;
|
||||
}
|
||||
|
||||
routerLinkFn = (dossier: DossierWrapper) => ['/main/dossiers/' + dossier.dossierId];
|
||||
|
||||
ngOnInit(): void {
|
||||
this._configureTableColumns();
|
||||
this.calculateData();
|
||||
|
||||
this.addSubscription = timer(0, 10000).subscribe(async () => {
|
||||
@ -112,7 +101,9 @@ export class DossierListingScreenComponent
|
||||
}
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
this.addSubscription = this._scrollViewport.scrolledIndexChange.pipe(tap(index => (this._lastScrolledIndex = index))).subscribe();
|
||||
this.addSubscription = this._tableComponent.scrollViewport.scrolledIndexChange
|
||||
.pipe(tap(index => (this._lastScrolledIndex = index)))
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
ngOnAttach(): void {
|
||||
@ -120,7 +111,7 @@ export class DossierListingScreenComponent
|
||||
this._loadEntitiesFromState();
|
||||
this.ngOnInit();
|
||||
this.ngAfterViewInit();
|
||||
this._scrollViewport.scrollToIndex(this._lastScrolledIndex, 'smooth');
|
||||
this._tableComponent.scrollViewport.scrollToIndex(this._lastScrolledIndex, 'smooth');
|
||||
}
|
||||
|
||||
ngOnDetach(): void {
|
||||
@ -161,6 +152,32 @@ export class DossierListingScreenComponent
|
||||
this.documentsChartData = this._translateChartService.translateStatus(this.documentsChartData);
|
||||
}
|
||||
|
||||
private _configureTableColumns() {
|
||||
this.tableColumnConfigs = [
|
||||
{
|
||||
label: _('dossier-listing.table-col-names.name'),
|
||||
sortByKey: 'dossierName',
|
||||
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);
|
||||
}
|
||||
@ -232,7 +249,7 @@ export class DossierListingScreenComponent
|
||||
slug: 'needsWorkFilters',
|
||||
label: this._translateService.instant('filters.needs-work'),
|
||||
icon: 'red:needs-work',
|
||||
filterTemplate: this._needsWorkTemplate,
|
||||
filterTemplate: this._needsWorkFilterTemplate,
|
||||
filters: needsWorkFilters.sort(RedactionFilterSorter.byKey),
|
||||
checker: annotationFilterChecker,
|
||||
matchAll: true
|
||||
|
||||
@ -16,18 +16,18 @@
|
||||
*ngIf="permissionsService.displayReanalyseBtn()"
|
||||
[tooltipClass]="'small ' + ((entitiesService.areSomeSelected$ | async) ? '' : 'warn')"
|
||||
[tooltip]="'dossier-overview.new-rule.toast.actions.reanalyse-all' | translate"
|
||||
[type]="circleButtonTypes.warn"
|
||||
icon="iqser:refresh"
|
||||
tooltipPosition="below"
|
||||
[type]="circleButtonTypes.warn"
|
||||
></iqser-circle-button>
|
||||
|
||||
<iqser-circle-button
|
||||
(action)="fileInput.click()"
|
||||
[tooltip]="'dossier-overview.header-actions.upload-document' | translate"
|
||||
[type]="circleButtonTypes.primary"
|
||||
class="ml-14"
|
||||
icon="red:upload"
|
||||
tooltipPosition="below"
|
||||
[type]="circleButtonTypes.primary"
|
||||
></iqser-circle-button>
|
||||
</redaction-page-header>
|
||||
|
||||
@ -35,146 +35,23 @@
|
||||
|
||||
<div class="red-content-inner">
|
||||
<div [class.extended]="collapsedDetails" class="content-container">
|
||||
<iqser-table-header
|
||||
<iqser-table
|
||||
(noDataAction)="fileInput.click()"
|
||||
[bulkActions]="bulkActions"
|
||||
[hasScrollButton]="true"
|
||||
[itemSize]="80"
|
||||
[noDataButtonLabel]="'dossier-overview.no-data.action' | translate"
|
||||
[noDataText]="'dossier-overview.no-data.title' | translate"
|
||||
[noMatchText]="'dossier-overview.no-match.title' | translate"
|
||||
[selectionEnabled]="true"
|
||||
[tableColumnConfigs]="tableColumnConfigs"
|
||||
[tableHeaderLabel]="tableHeaderLabel"
|
||||
></iqser-table-header>
|
||||
|
||||
<redaction-empty-state
|
||||
(action)="fileInput.click()"
|
||||
*ngIf="entitiesService.noData$ | async"
|
||||
[buttonLabel]="'dossier-overview.no-data.action' | translate"
|
||||
[text]="'dossier-overview.no-data.title' | translate"
|
||||
buttonIcon="red:upload"
|
||||
icon="red:document"
|
||||
></redaction-empty-state>
|
||||
|
||||
<redaction-empty-state *ngIf="noMatch$ | async" [text]="'dossier-overview.no-match.title' | translate"></redaction-empty-state>
|
||||
|
||||
<cdk-virtual-scroll-viewport #scrollViewport [itemSize]="itemSize" redactionHasScrollbar>
|
||||
<div
|
||||
*cdkVirtualFor="let fileStatus of sortedDisplayedEntities$ | async; trackBy: trackByPrimaryKey"
|
||||
[class.disabled]="fileStatus.excluded"
|
||||
[class.last-opened]="fileStatus.lastOpened"
|
||||
[class.pointer]="fileStatus.canBeOpened"
|
||||
[routerLink]="fileLink(fileStatus)"
|
||||
class="table-item"
|
||||
>
|
||||
<div (click)="toggleEntitySelected($event, fileStatus)" class="selection-column">
|
||||
<iqser-round-checkbox [active]="isSelected(fileStatus)"></iqser-round-checkbox>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="filename-wrapper">
|
||||
<div
|
||||
[class.disabled]="fileStatus.isPending"
|
||||
[class.error]="fileStatus.isError"
|
||||
class="table-item-title text-overflow"
|
||||
>
|
||||
<span [matTooltip]="fileStatus.filename" matTooltipPosition="above">
|
||||
{{ fileStatus.filename }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="fileStatus.primaryAttribute" class="small-label stats-subtitle">
|
||||
<div class="primary-attribute text-overflow">
|
||||
<span [matTooltip]="fileStatus.primaryAttribute" matTooltipPosition="above">
|
||||
{{ fileStatus.primaryAttribute }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="small-label stats-subtitle">
|
||||
<div>
|
||||
<mat-icon svgIcon="red:pages"></mat-icon>
|
||||
{{ fileStatus.numberOfPages }}
|
||||
</div>
|
||||
<div>
|
||||
<mat-icon svgIcon="red:exclude-pages"></mat-icon>
|
||||
{{ fileStatus.excludedPagesCount }}
|
||||
</div>
|
||||
<div
|
||||
*ngIf="fileStatus.lastOCRTime"
|
||||
[matTooltipPosition]="'above'"
|
||||
[matTooltip]="'dossier-overview.ocr-performed' | translate"
|
||||
>
|
||||
<mat-icon svgIcon="red:ocr"></mat-icon>
|
||||
{{ fileStatus.lastOCRTime | date: 'mediumDate' }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div [class.error]="fileStatus.isError" class="small-label">
|
||||
{{ fileStatus.added | date: 'd MMM. yyyy, hh:mm a' }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div *ngFor="let config of displayedInFileListAttributes">
|
||||
{{ fileStatus.fileAttributes.attributeIdToValue[config.id] }}
|
||||
</div>
|
||||
|
||||
<!-- always show A for error-->
|
||||
<div *ngIf="fileStatus.isError">
|
||||
<redaction-annotation-icon color="#dd4d50" label="A" type="square"></redaction-annotation-icon>
|
||||
</div>
|
||||
|
||||
<div *ngIf="!fileStatus.isError">
|
||||
<redaction-needs-work-badge [needsWorkInput]="fileStatus"></redaction-needs-work-badge>
|
||||
</div>
|
||||
<div *ngIf="!fileStatus.isError" class="user-column">
|
||||
<redaction-initials-avatar [userId]="fileStatus.currentReviewer" [withName]="true"></redaction-initials-avatar>
|
||||
</div>
|
||||
|
||||
<div *ngIf="!fileStatus.isError">
|
||||
<div class="quick-navigation">
|
||||
<mat-icon svgIcon="red:pages"></mat-icon>
|
||||
{{ fileStatus.numberOfPages }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div [class.extend-cols]="fileStatus.isError" class="status-container">
|
||||
<div
|
||||
*ngIf="fileStatus.isError"
|
||||
class="small-label error"
|
||||
translate="dossier-overview.file-listing.file-entry.file-error"
|
||||
></div>
|
||||
<div
|
||||
*ngIf="fileStatus.isPending"
|
||||
class="small-label"
|
||||
translate="dossier-overview.file-listing.file-entry.file-pending"
|
||||
></div>
|
||||
<div
|
||||
*ngIf="fileStatus.isProcessing"
|
||||
class="small-label loading"
|
||||
translate="dossier-overview.file-listing.file-entry.file-processing"
|
||||
></div>
|
||||
<iqser-status-bar
|
||||
*ngIf="fileStatus.isWorkable"
|
||||
[configs]="[
|
||||
{
|
||||
color: fileStatus.status,
|
||||
length: 1
|
||||
}
|
||||
]"
|
||||
></iqser-status-bar>
|
||||
|
||||
<redaction-file-actions
|
||||
(actionPerformed)="calculateData()"
|
||||
*ngIf="!fileStatus.isProcessing"
|
||||
[fileStatus]="fileStatus"
|
||||
class="mr-4"
|
||||
></redaction-file-actions>
|
||||
</div>
|
||||
<div class="scrollbar-placeholder"></div>
|
||||
</div>
|
||||
</cdk-virtual-scroll-viewport>
|
||||
|
||||
<redaction-scroll-button [itemSize]="itemSize" [scrollViewport]="scrollViewport"></redaction-scroll-button>
|
||||
[showNoDataButton]="true"
|
||||
[tableItemClasses]="{ disabled: disabledFn, 'last-opened': lastOpenedFn }"
|
||||
noDataButtonIcon="red:upload"
|
||||
noDataIcon="red:document"
|
||||
></iqser-table>
|
||||
</div>
|
||||
|
||||
<div [class.collapsed]="collapsedDetails" class="right-container" redactionHasScrollbar>
|
||||
<div [class.collapsed]="collapsedDetails" class="right-container" iqserHasScrollbar>
|
||||
<redaction-dossier-details
|
||||
(openAssignDossierMembersDialog)="openAssignDossierMembersDialog()"
|
||||
(openDossierDictionaryDialog)="openDossierDictionaryDialog()"
|
||||
@ -185,7 +62,7 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<ng-template #needsWorkTemplate let-filter="filter">
|
||||
<ng-template #needsWorkFilterTemplate let-filter="filter">
|
||||
<redaction-type-filter [filter]="filter"></redaction-type-filter>
|
||||
</ng-template>
|
||||
|
||||
@ -194,3 +71,106 @@
|
||||
<ng-template #bulkActions>
|
||||
<redaction-dossier-overview-bulk-actions (reload)="bulkActionPerformed()"></redaction-dossier-overview-bulk-actions>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #filenameTemplate let-fileStatus="entity">
|
||||
<div class="cell">
|
||||
<div class="filename-wrapper">
|
||||
<div [class.error]="fileStatus.isError" class="table-item-title text-overflow">
|
||||
<span [matTooltip]="fileStatus.filename" matTooltipPosition="above">
|
||||
{{ fileStatus.filename }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="fileStatus.primaryAttribute" class="small-label">
|
||||
<div class="primary-attribute text-overflow">
|
||||
<span [matTooltip]="fileStatus.primaryAttribute" matTooltipPosition="above">
|
||||
{{ fileStatus.primaryAttribute }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="small-label stats-subtitle">
|
||||
<div>
|
||||
<mat-icon svgIcon="red:pages"></mat-icon>
|
||||
{{ fileStatus.numberOfPages }}
|
||||
</div>
|
||||
<div>
|
||||
<mat-icon svgIcon="red:exclude-pages"></mat-icon>
|
||||
{{ fileStatus.excludedPagesCount }}
|
||||
</div>
|
||||
<div *ngIf="fileStatus.lastOCRTime" [matTooltipPosition]="'above'" [matTooltip]="'dossier-overview.ocr-performed' | translate">
|
||||
<mat-icon svgIcon="red:ocr"></mat-icon>
|
||||
{{ fileStatus.lastOCRTime | date: 'mediumDate' }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #addedOnTemplate let-fileStatus="entity">
|
||||
<div class="cell">
|
||||
<div [class.error]="fileStatus.isError" class="small-label">
|
||||
{{ fileStatus.added | date: 'd MMM. yyyy, hh:mm a' }}
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #attributeTemplate let-config="extra" let-fileStatus="entity">
|
||||
<div class="cell">
|
||||
{{ fileStatus.fileAttributes.attributeIdToValue[config.id] }}
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #needsWorkTemplate let-fileStatus="entity">
|
||||
<!-- always show A for error-->
|
||||
<div *ngIf="fileStatus.isError" class="cell">
|
||||
<redaction-annotation-icon color="#dd4d50" label="A" type="square"></redaction-annotation-icon>
|
||||
</div>
|
||||
|
||||
<div *ngIf="!fileStatus.isError" class="cell">
|
||||
<redaction-needs-work-badge [needsWorkInput]="fileStatus"></redaction-needs-work-badge>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #reviewerTemplate let-fileStatus="entity">
|
||||
<div *ngIf="!fileStatus.isError" class="user-column cell">
|
||||
<redaction-initials-avatar [userId]="fileStatus.currentReviewer" [withName]="true"></redaction-initials-avatar>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #pagesTemplate let-fileStatus="entity">
|
||||
<div *ngIf="!fileStatus.isError" class="cell">
|
||||
<div class="small-label stats-subtitle">
|
||||
<div>
|
||||
<mat-icon svgIcon="red:pages"></mat-icon>
|
||||
{{ fileStatus.numberOfPages }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #statusTemplate let-fileStatus="entity">
|
||||
<div [class.extend-cols]="fileStatus.isError" class="status-container cell">
|
||||
<div *ngIf="fileStatus.isError" class="small-label error" translate="dossier-overview.file-listing.file-entry.file-error"></div>
|
||||
<div *ngIf="fileStatus.isPending" class="small-label" translate="dossier-overview.file-listing.file-entry.file-pending"></div>
|
||||
<div
|
||||
*ngIf="fileStatus.isProcessing"
|
||||
class="small-label loading"
|
||||
translate="dossier-overview.file-listing.file-entry.file-processing"
|
||||
></div>
|
||||
<iqser-status-bar
|
||||
*ngIf="fileStatus.isWorkable"
|
||||
[configs]="[
|
||||
{
|
||||
color: fileStatus.status,
|
||||
length: 1
|
||||
}
|
||||
]"
|
||||
></iqser-status-bar>
|
||||
|
||||
<redaction-file-actions
|
||||
(actionPerformed)="calculateData()"
|
||||
*ngIf="!fileStatus.isProcessing"
|
||||
[fileStatus]="fileStatus"
|
||||
class="mr-4"
|
||||
></redaction-file-actions>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
@ -1,66 +1,41 @@
|
||||
@import '../../../../../assets/styles/variables';
|
||||
|
||||
:root {
|
||||
--dynamic-columns: '1fr';
|
||||
}
|
||||
|
||||
.file-upload-input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
cdk-virtual-scroll-viewport {
|
||||
::ng-deep.cdk-virtual-scroll-content-wrapper {
|
||||
grid-template-columns: auto 3fr 2fr repeat(var(--dynamic-columns, 1), 1fr) 2fr 1fr auto 11px;
|
||||
:host ::ng-deep iqser-table cdk-virtual-scroll-viewport .cdk-virtual-scroll-content-wrapper .table-item {
|
||||
&.last-opened {
|
||||
> .selection-column {
|
||||
padding-left: 6px !important;
|
||||
border-left: 4px solid $primary;
|
||||
}
|
||||
|
||||
.table-item {
|
||||
> div {
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.disabled {
|
||||
color: $grey-7;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: $primary;
|
||||
}
|
||||
|
||||
.extend-cols {
|
||||
grid-column-end: span 3;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.table-item-title {
|
||||
max-width: 25vw;
|
||||
}
|
||||
|
||||
.quick-navigation {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
opacity: 0.7;
|
||||
color: $accent;
|
||||
font-size: 11px;
|
||||
letter-spacing: 0;
|
||||
line-height: 14px;
|
||||
|
||||
.mat-icon {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.status-container {
|
||||
align-items: flex-end;
|
||||
}
|
||||
> div {
|
||||
animation: red-fading-background 3s 1;
|
||||
}
|
||||
}
|
||||
|
||||
&.has-scrollbar:hover {
|
||||
::ng-deep.cdk-virtual-scroll-content-wrapper {
|
||||
grid-template-columns: auto 3fr 2fr repeat(var(--dynamic-columns, 1), 1fr) 2fr 1fr auto;
|
||||
> div.cell {
|
||||
.error {
|
||||
color: $primary;
|
||||
}
|
||||
|
||||
.table-item-title {
|
||||
max-width: 25vw;
|
||||
}
|
||||
|
||||
.primary-attribute {
|
||||
padding-top: 6px;
|
||||
}
|
||||
|
||||
&.extend-cols {
|
||||
grid-column-end: span 3;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
&.status-container {
|
||||
align-items: flex-end;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -80,45 +55,6 @@ cdk-virtual-scroll-viewport {
|
||||
}
|
||||
}
|
||||
|
||||
.reanalyse-link {
|
||||
color: $accent;
|
||||
text-decoration: underline;
|
||||
|
||||
&:hover {
|
||||
color: lighten($accent, 10%);
|
||||
}
|
||||
}
|
||||
|
||||
.mr-4 {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.disabled {
|
||||
> div {
|
||||
background-color: $grey-2;
|
||||
color: $grey-7;
|
||||
}
|
||||
|
||||
redaction-file-actions {
|
||||
color: initial;
|
||||
}
|
||||
}
|
||||
|
||||
.primary-attribute {
|
||||
padding-top: 6px;
|
||||
}
|
||||
|
||||
.last-opened {
|
||||
> .selection-column {
|
||||
padding-left: 6px !important;
|
||||
border-left: 4px solid $primary;
|
||||
}
|
||||
|
||||
> div {
|
||||
animation: red-fading-background 3s 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes red-fading-background {
|
||||
0% {
|
||||
background-color: rgba($primary, 0.1);
|
||||
|
||||
@ -1,4 +1,15 @@
|
||||
import { ChangeDetectorRef, Component, ElementRef, HostListener, Injector, OnDestroy, OnInit, TemplateRef, ViewChild } from '@angular/core';
|
||||
import {
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
ElementRef,
|
||||
forwardRef,
|
||||
HostListener,
|
||||
Injector,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
TemplateRef,
|
||||
ViewChild
|
||||
} from '@angular/core';
|
||||
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';
|
||||
@ -26,6 +37,7 @@ import {
|
||||
LoadingService,
|
||||
NestedFilter,
|
||||
TableColumnConfig,
|
||||
TableComponent,
|
||||
Toaster
|
||||
} from '@iqser/common-ui';
|
||||
import { DossierAttributesService } from '@shared/services/controller-wrappers/dossier-attributes.service';
|
||||
@ -36,17 +48,16 @@ import { fileStatusTranslations } from '../../translations/file-status-translati
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { annotationFilterChecker } from '@utils/filter-utils';
|
||||
import { PermissionsService } from '@services/permissions.service';
|
||||
import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
|
||||
import { RouterHistoryService } from '@services/router-history.service';
|
||||
import { FileAttributeConfig } from '@redaction/red-ui-http';
|
||||
import { DossierWrapper } from '../../../../state/model/dossier.wrapper';
|
||||
|
||||
@Component({
|
||||
templateUrl: './dossier-overview-screen.component.html',
|
||||
styleUrls: ['./dossier-overview-screen.component.scss'],
|
||||
providers: [...DefaultListingServices]
|
||||
providers: [...DefaultListingServices, { provide: ListingComponent, useExisting: forwardRef(() => DossierOverviewScreenComponent) }]
|
||||
})
|
||||
export class DossierOverviewScreenComponent extends ListingComponent<FileStatusWrapper> implements OnInit, OnDestroy, OnDetach, OnAttach {
|
||||
readonly itemSize = 80;
|
||||
readonly circleButtonTypes = CircleButtonTypes;
|
||||
readonly currentUser = this._userService.currentUser;
|
||||
currentDossier = this._appStateService.activeDossier;
|
||||
@ -63,43 +74,21 @@ export class DossierOverviewScreenComponent extends ListingComponent<FileStatusW
|
||||
collapsedDetails = false;
|
||||
dossierAttributes: DossierAttributeWithValue[] = [];
|
||||
fileAttributeConfigs: FileAttributeConfig[];
|
||||
dynamicColumnsCount = 0;
|
||||
@ViewChild('filenameTemplate', { static: true }) filenameTemplate: TemplateRef<never>;
|
||||
@ViewChild('addedOnTemplate', { static: true }) addedOnTemplate: TemplateRef<never>;
|
||||
@ViewChild('attributeTemplate', { static: true }) attributeTemplate: TemplateRef<never>;
|
||||
@ViewChild('needsWorkTemplate', { static: true }) needsWorkTemplate: TemplateRef<never>;
|
||||
@ViewChild('reviewerTemplate', { static: true }) reviewerTemplate: TemplateRef<never>;
|
||||
@ViewChild('pagesTemplate', { static: true }) pagesTemplate: TemplateRef<never>;
|
||||
@ViewChild('statusTemplate', { static: true }) statusTemplate: TemplateRef<never>;
|
||||
protected readonly _primaryKey = 'filename';
|
||||
private readonly _defaultTableConfigs: readonly TableColumnConfig<FileStatusWrapper>[] = [
|
||||
{
|
||||
label: _('dossier-overview.table-col-names.name'),
|
||||
sortByKey: 'filename'
|
||||
},
|
||||
{
|
||||
label: _('dossier-overview.table-col-names.added-on'),
|
||||
sortByKey: 'added'
|
||||
},
|
||||
{
|
||||
label: _('dossier-overview.table-col-names.needs-work')
|
||||
},
|
||||
{
|
||||
label: _('dossier-overview.table-col-names.assigned-to'),
|
||||
class: 'user-column',
|
||||
sortByKey: 'reviewerName'
|
||||
},
|
||||
{
|
||||
label: _('dossier-overview.table-col-names.pages'),
|
||||
sortByKey: 'pages'
|
||||
},
|
||||
{
|
||||
label: _('dossier-overview.table-col-names.status'),
|
||||
class: 'flex-end',
|
||||
sortByKey: 'statusSort'
|
||||
}
|
||||
];
|
||||
@ViewChild(DossierDetailsComponent, { static: false })
|
||||
private readonly _dossierDetailsComponent: DossierDetailsComponent;
|
||||
private _lastScrolledIndex: number;
|
||||
@ViewChild('needsWorkTemplate', { read: TemplateRef, static: true })
|
||||
private readonly _needsWorkTemplate: TemplateRef<unknown>;
|
||||
@ViewChild('needsWorkFilterTemplate', { read: TemplateRef, static: true })
|
||||
private readonly _needsWorkFilterTemplate: TemplateRef<unknown>;
|
||||
@ViewChild('fileInput') private readonly _fileInput: ElementRef;
|
||||
@ViewChild(CdkVirtualScrollViewport)
|
||||
private readonly _scrollViewport: CdkVirtualScrollViewport;
|
||||
@ViewChild(TableComponent) private readonly _tableComponent: TableComponent<DossierWrapper>;
|
||||
|
||||
constructor(
|
||||
private readonly _toaster: Toaster,
|
||||
@ -138,7 +127,14 @@ export class DossierOverviewScreenComponent extends ListingComponent<FileStatusW
|
||||
return this.fileAttributeConfigs.filter(config => config.displayedInFileList);
|
||||
}
|
||||
|
||||
routerLinkFn = (fileStatus: FileStatusWrapper) =>
|
||||
fileStatus.canBeOpened ? [`/main/dossiers/${this.currentDossier.dossierId}/file/${fileStatus.fileId}`] : [];
|
||||
|
||||
disabledFn = (fileStatus: FileStatusWrapper) => fileStatus.excluded;
|
||||
lastOpenedFn = (fileStatus: FileStatusWrapper) => fileStatus.lastOpened;
|
||||
|
||||
async ngOnInit(): Promise<void> {
|
||||
this._configureTableColumns();
|
||||
this._loadingService.start();
|
||||
try {
|
||||
this._fileDropOverlayService.initFileDropHandling();
|
||||
@ -160,12 +156,11 @@ export class DossierOverviewScreenComponent extends ListingComponent<FileStatusW
|
||||
).fileAttributeConfigs;
|
||||
});
|
||||
|
||||
this.addSubscription = this._scrollViewport.scrolledIndexChange
|
||||
this.addSubscription = this._tableComponent.scrollViewport.scrolledIndexChange
|
||||
.pipe(tap(index => (this._lastScrolledIndex = index)))
|
||||
.subscribe();
|
||||
|
||||
this.searchService.setSearchKey('filename');
|
||||
this._configureTableColumns();
|
||||
|
||||
this.dossierAttributes = await this._dossierAttributesService.getValues(this.currentDossier);
|
||||
} catch (e) {
|
||||
@ -183,7 +178,7 @@ export class DossierOverviewScreenComponent extends ListingComponent<FileStatusW
|
||||
await this._appStateService.reloadActiveDossierFiles();
|
||||
this._loadEntitiesFromState();
|
||||
await this.ngOnInit();
|
||||
this._scrollViewport.scrollToIndex(this._lastScrolledIndex, 'smooth');
|
||||
this._tableComponent.scrollViewport.scrollToIndex(this._lastScrolledIndex, 'smooth');
|
||||
}
|
||||
|
||||
ngOnDetach() {
|
||||
@ -233,10 +228,6 @@ export class DossierOverviewScreenComponent extends ListingComponent<FileStatusW
|
||||
this._fileInput.nativeElement.value = null;
|
||||
}
|
||||
|
||||
fileLink(fileStatus: FileStatusWrapper) {
|
||||
return fileStatus.canBeOpened ? [`/main/dossiers/${this.currentDossier.dossierId}/file/${fileStatus.fileId}`] : [];
|
||||
}
|
||||
|
||||
async bulkActionPerformed() {
|
||||
this.entitiesService.setSelected([]);
|
||||
await this.reloadDossiers();
|
||||
@ -271,17 +262,46 @@ export class DossierOverviewScreenComponent extends ListingComponent<FileStatusW
|
||||
const dynamicColumns: TableColumnConfig<FileStatusWrapper>[] = [];
|
||||
for (const config of this.displayedInFileListAttributes) {
|
||||
if (config.displayedInFileList) {
|
||||
dynamicColumns.push({ label: config.label, notTranslatable: true });
|
||||
dynamicColumns.push({ label: config.label, notTranslatable: true, template: this.attributeTemplate, extra: config });
|
||||
}
|
||||
}
|
||||
this.tableColumnConfigs = [
|
||||
this._defaultTableConfigs[0],
|
||||
this._defaultTableConfigs[1],
|
||||
{
|
||||
label: _('dossier-overview.table-col-names.name'),
|
||||
sortByKey: 'filename',
|
||||
template: this.filenameTemplate,
|
||||
width: '3fr'
|
||||
},
|
||||
{
|
||||
label: _('dossier-overview.table-col-names.added-on'),
|
||||
sortByKey: 'added',
|
||||
template: this.addedOnTemplate,
|
||||
width: '2fr'
|
||||
},
|
||||
...dynamicColumns,
|
||||
...this._defaultTableConfigs.slice(2)
|
||||
{
|
||||
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: 'pages',
|
||||
template: this.pagesTemplate
|
||||
},
|
||||
{
|
||||
label: _('dossier-overview.table-col-names.status'),
|
||||
class: 'flex-end',
|
||||
sortByKey: 'statusSort',
|
||||
template: this.statusTemplate
|
||||
}
|
||||
];
|
||||
this.dynamicColumnsCount = dynamicColumns.length + 1; // zero is not allowed in repeat, by default we repeat 1 column
|
||||
document.documentElement.style.setProperty('--dynamic-columns', `${this.dynamicColumnsCount}`);
|
||||
}
|
||||
|
||||
private _loadEntitiesFromState() {
|
||||
@ -404,7 +424,7 @@ export class DossierOverviewScreenComponent extends ListingComponent<FileStatusW
|
||||
slug: 'needsWorkFilters',
|
||||
label: this._translateService.instant('filters.needs-work'),
|
||||
icon: 'red:needs-work',
|
||||
filterTemplate: this._needsWorkTemplate,
|
||||
filterTemplate: this._needsWorkFilterTemplate,
|
||||
filters: needsWorkFilters.sort(RedactionFilterSorter.byKey),
|
||||
checker: annotationFilterChecker,
|
||||
matchAll: true
|
||||
|
||||
@ -147,12 +147,12 @@
|
||||
</div>
|
||||
|
||||
<div class="right-container">
|
||||
<redaction-empty-state
|
||||
<iqser-empty-state
|
||||
*ngIf="appStateService.activeFile.excluded && !viewDocumentInfo && !excludePages"
|
||||
[horizontalPadding]="40"
|
||||
[text]="'file-preview.tabs.is-excluded' | translate"
|
||||
icon="red:needs-work"
|
||||
></redaction-empty-state>
|
||||
></iqser-empty-state>
|
||||
|
||||
<redaction-document-info
|
||||
(closeDocumentInfoView)="viewDocumentInfo = false"
|
||||
|
||||
@ -2,97 +2,85 @@
|
||||
<redaction-page-header
|
||||
(closeAction)="routerHistoryService.navigateToLastDossiersScreen()"
|
||||
[searchPlaceholder]="'search.placeholder' | translate"
|
||||
[searchPosition]="searchPositions.beforeFilters"
|
||||
[searchWidth]="600"
|
||||
[showCloseButton]="true"
|
||||
[searchPosition]="searchPositions.beforeFilters"
|
||||
></redaction-page-header>
|
||||
|
||||
<div class="overlay-shadow"></div>
|
||||
|
||||
<div class="red-content-inner">
|
||||
<div class="content-container">
|
||||
<iqser-table-header [tableColumnConfigs]="tableColumnConfigs" [tableHeaderLabel]="tableHeaderLabel"></iqser-table-header>
|
||||
|
||||
<redaction-empty-state
|
||||
*ngIf="searchResult.length === 0"
|
||||
[icon]="'iqser:search'"
|
||||
[text]="'search-screen.no-data' | translate"
|
||||
></redaction-empty-state>
|
||||
|
||||
<cdk-virtual-scroll-viewport #scrollViewport [itemSize]="itemSize" redactionHasScrollbar>
|
||||
<div
|
||||
*cdkVirtualFor="let item of sortedDisplayedEntities$ | async; trackBy: trackByPrimaryKey"
|
||||
[class.pointer]="true"
|
||||
[routerLink]="item.routerLink"
|
||||
class="table-item"
|
||||
>
|
||||
<div class="filename">
|
||||
<div [matTooltip]="item.filename" class="table-item-title heading" matTooltipPosition="above">
|
||||
<span
|
||||
*ngIf="item.highlights.filename; else defaultFilename"
|
||||
[innerHTML]="item.highlights.filename[0]"
|
||||
class="highlights"
|
||||
></span>
|
||||
<ng-template #defaultFilename>{{ item.filename }}</ng-template>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
|
||||
<div>
|
||||
<iqser-status-bar
|
||||
[configs]="[
|
||||
{
|
||||
color: item.status,
|
||||
label: fileStatusTranslations[item.status] | translate,
|
||||
length: 1,
|
||||
cssClass: 'all-caps-label'
|
||||
}
|
||||
]"
|
||||
[small]="true"
|
||||
></iqser-status-bar>
|
||||
</div>
|
||||
|
||||
<div class="small-label">
|
||||
{{ item.dossierName }}
|
||||
</div>
|
||||
|
||||
<div class="small-label stats-subtitle">
|
||||
<div>
|
||||
<mat-icon svgIcon="red:pages"></mat-icon>
|
||||
{{ item.numberOfPages }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="scrollbar-placeholder"></div>
|
||||
</div>
|
||||
</cdk-virtual-scroll-viewport>
|
||||
|
||||
<redaction-scroll-button
|
||||
*ngIf="searchResult.length"
|
||||
[itemSize]="itemSize"
|
||||
[scrollViewport]="scrollViewport"
|
||||
></redaction-scroll-button>
|
||||
<iqser-table
|
||||
[hasScrollButton]="true"
|
||||
[itemSize]="85"
|
||||
[noDataText]="'search-screen.no-data' | translate"
|
||||
noDataIcon="iqser:search"
|
||||
></iqser-table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<ng-template #filenameTemplate let-item="entity">
|
||||
<div class="cell filename">
|
||||
<div [matTooltip]="item.filename" class="table-item-title heading" matTooltipPosition="above">
|
||||
<span
|
||||
*ngIf="item.highlights.filename; else defaultFilename"
|
||||
[innerHTML]="item.highlights.filename[0]"
|
||||
class="highlights"
|
||||
></span>
|
||||
<ng-template #defaultFilename>{{ item.filename }}</ng-template>
|
||||
</div>
|
||||
|
||||
<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-template>
|
||||
|
||||
<ng-template #statusTemplate let-item="entity">
|
||||
<div class="cell">
|
||||
<iqser-status-bar
|
||||
[configs]="[
|
||||
{
|
||||
color: item.status,
|
||||
label: fileStatusTranslations[item.status] | translate,
|
||||
length: 1,
|
||||
cssClass: 'all-caps-label'
|
||||
}
|
||||
]"
|
||||
[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>
|
||||
</ng-template>
|
||||
|
||||
@ -1,42 +1,12 @@
|
||||
@import 'libs/common-ui/src/assets/styles/mixins';
|
||||
@import '../../../../../assets/styles/variables';
|
||||
|
||||
.content-container {
|
||||
position: relative;
|
||||
:host ::ng-deep iqser-table cdk-virtual-scroll-viewport .cdk-virtual-scroll-content-wrapper .table-item > div.cell {
|
||||
.highlights {
|
||||
@include line-clamp(1);
|
||||
|
||||
cdk-virtual-scroll-viewport {
|
||||
::ng-deep.cdk-virtual-scroll-content-wrapper {
|
||||
grid-template-columns: 2fr 1fr 1fr auto 11px;
|
||||
|
||||
.table-item {
|
||||
> div {
|
||||
height: 85px;
|
||||
padding: 0 24px;
|
||||
}
|
||||
|
||||
.status-container {
|
||||
width: 160px;
|
||||
padding-right: 13px;
|
||||
}
|
||||
|
||||
.highlights em {
|
||||
background-color: #fffcc4;
|
||||
}
|
||||
|
||||
.highlights {
|
||||
@include line-clamp(1);
|
||||
}
|
||||
|
||||
.stats-subtitle > div {
|
||||
width: fit-content;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.has-scrollbar:hover {
|
||||
::ng-deep.cdk-virtual-scroll-content-wrapper {
|
||||
grid-template-columns: 2fr 1fr 1fr auto;
|
||||
}
|
||||
em {
|
||||
background-color: #fffcc4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Component, Injector, OnDestroy } from '@angular/core';
|
||||
import { Component, forwardRef, Injector, OnDestroy, OnInit, TemplateRef, ViewChild } from '@angular/core';
|
||||
import { DefaultListingServices, keyChecker, Listable, ListingComponent, LoadingService, TableColumnConfig } from '@iqser/common-ui';
|
||||
import { MatchedDocument, SearchControllerService, SearchResult } from '@redaction/red-ui-http';
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
@ -32,20 +32,17 @@ interface SearchInput {
|
||||
@Component({
|
||||
templateUrl: './search-screen.component.html',
|
||||
styleUrls: ['./search-screen.component.scss'],
|
||||
providers: [...DefaultListingServices]
|
||||
providers: [...DefaultListingServices, { provide: ListingComponent, useExisting: forwardRef(() => SearchScreenComponent) }]
|
||||
})
|
||||
export class SearchScreenComponent extends ListingComponent<ListItem> implements OnDestroy {
|
||||
export class SearchScreenComponent extends ListingComponent<ListItem> implements OnDestroy, OnInit {
|
||||
readonly fileStatusTranslations = fileStatusTranslations;
|
||||
readonly searchPositions = SearchPositions;
|
||||
|
||||
readonly itemSize = 85;
|
||||
@ViewChild('filenameTemplate', { static: true }) filenameTemplate: TemplateRef<never>;
|
||||
@ViewChild('statusTemplate', { static: true }) statusTemplate: TemplateRef<never>;
|
||||
@ViewChild('dossierTemplate', { static: true }) dossierTemplate: TemplateRef<never>;
|
||||
@ViewChild('pagesTemplate', { static: true }) pagesTemplate: TemplateRef<never>;
|
||||
readonly tableHeaderLabel = _('search-screen.table-header');
|
||||
readonly tableColumnConfigs: TableColumnConfig<ListItem>[] = [
|
||||
{ label: _('search-screen.cols.document') },
|
||||
{ label: _('search-screen.cols.status') },
|
||||
{ label: _('search-screen.cols.dossier') },
|
||||
{ label: _('search-screen.cols.pages') }
|
||||
];
|
||||
tableColumnConfigs: TableColumnConfig<ListItem>[];
|
||||
readonly search$ = new BehaviorSubject<SearchInput>(null);
|
||||
readonly searchResults$: Observable<ListItem[]> = this.search$.asObservable().pipe(
|
||||
switchMap(query => this._search(query)),
|
||||
@ -94,6 +91,8 @@ export class SearchScreenComponent extends ListingComponent<ListItem> implements
|
||||
});
|
||||
}
|
||||
|
||||
routerLinkFn = (entity: ListItem) => [entity.routerLink];
|
||||
|
||||
setInitialConfig(): void {
|
||||
return;
|
||||
}
|
||||
@ -104,6 +103,19 @@ export class SearchScreenComponent extends ListingComponent<ListItem> implements
|
||||
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> {
|
||||
return this._searchControllerService.search({
|
||||
dossierIds: searchInput.dossierIds,
|
||||
|
||||
@ -14,7 +14,6 @@ export class IconsModule {
|
||||
'add',
|
||||
'analyse',
|
||||
'approved',
|
||||
'arrow-down-o',
|
||||
'arrow-right',
|
||||
'arrow-up',
|
||||
'assign',
|
||||
|
||||
@ -1,21 +0,0 @@
|
||||
<div
|
||||
[ngStyle]="{
|
||||
'padding-top': verticalPadding + 'px',
|
||||
'padding-left': horizontalPadding + 'px',
|
||||
'padding-right': horizontalPadding + 'px'
|
||||
}"
|
||||
class="empty-state"
|
||||
>
|
||||
<mat-icon *ngIf="icon" [svgIcon]="icon"></mat-icon>
|
||||
<div class="ng-content-wrapper heading-l">
|
||||
<ng-content></ng-content>
|
||||
</div>
|
||||
<div class="heading-l">{{ text }}</div>
|
||||
<iqser-icon-button
|
||||
(action)="action.emit()"
|
||||
*ngIf="showButton"
|
||||
[icon]="buttonIcon"
|
||||
[label]="buttonLabel"
|
||||
[type]="iconButtonTypes.primary"
|
||||
></iqser-icon-button>
|
||||
</div>
|
||||
@ -1,27 +0,0 @@
|
||||
@import '../../../../../assets/styles/variables';
|
||||
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
|
||||
> mat-icon {
|
||||
height: 60px;
|
||||
width: 60px;
|
||||
opacity: 0.1;
|
||||
}
|
||||
|
||||
.heading-l {
|
||||
color: $grey-7;
|
||||
}
|
||||
|
||||
> .heading-l,
|
||||
iqser-icon-button {
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.ng-content-wrapper:not(:empty) + .heading-l {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@ -1,25 +0,0 @@
|
||||
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
||||
import { IconButtonTypes } from '@iqser/common-ui';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-empty-state',
|
||||
templateUrl: './empty-state.component.html',
|
||||
styleUrls: ['./empty-state.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class EmptyStateComponent implements OnInit {
|
||||
readonly iconButtonTypes = IconButtonTypes;
|
||||
|
||||
@Input() text: string;
|
||||
@Input() icon: string;
|
||||
@Input() showButton = true;
|
||||
@Input() buttonIcon = 'red:plus';
|
||||
@Input() buttonLabel: string;
|
||||
@Input() horizontalPadding = 100;
|
||||
@Input() verticalPadding = 120;
|
||||
@Output() action = new EventEmitter();
|
||||
|
||||
ngOnInit(): void {
|
||||
this.showButton = this.showButton && this.action.observers.length > 0;
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
<div (click)="selectPage(currentPage - 1)" [class.disabled]="currentPage < 2" class="page" translate="pagination.previous"></div>
|
||||
<div (click)="selectPage(currentPage - 1)" [class.disabled]="currentPage < 1" class="page" translate="pagination.previous"></div>
|
||||
<span>|</span>
|
||||
<div
|
||||
(click)="selectPage(page)"
|
||||
|
||||
@ -1,11 +1,10 @@
|
||||
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
|
||||
const DISPLAYED_ITEMS = 5;
|
||||
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-pagination',
|
||||
templateUrl: './pagination.component.html',
|
||||
styleUrls: ['./pagination.component.scss']
|
||||
styleUrls: ['./pagination.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class PaginationComponent {
|
||||
displayedPages: (number | string)[];
|
||||
@ -31,10 +30,6 @@ export class PaginationComponent {
|
||||
this._updatePagesArray();
|
||||
}
|
||||
|
||||
get allDisplayed(): boolean {
|
||||
return this.totalPages > DISPLAYED_ITEMS;
|
||||
}
|
||||
|
||||
selectPage(page: number | string) {
|
||||
if (page !== '...') {
|
||||
this.pageChanged.emit(page as number);
|
||||
|
||||
@ -1,26 +0,0 @@
|
||||
import { AfterContentChecked, Directive, ElementRef, HostBinding } from '@angular/core';
|
||||
|
||||
@Directive({
|
||||
selector: '[redactionHasScrollbar]',
|
||||
exportAs: 'redactionHasScrollbar'
|
||||
})
|
||||
export class HasScrollbarDirective implements AfterContentChecked {
|
||||
@HostBinding('class') class = '';
|
||||
|
||||
constructor(private readonly _elementRef: ElementRef) {}
|
||||
|
||||
get hasScrollbar() {
|
||||
return this._elementRef?.nativeElement.clientHeight < this._elementRef?.nativeElement.scrollHeight;
|
||||
}
|
||||
|
||||
ngAfterContentChecked() {
|
||||
this._process();
|
||||
}
|
||||
|
||||
_process() {
|
||||
const newClass = this.hasScrollbar ? 'has-scrollbar' : '';
|
||||
if (this.class !== newClass) {
|
||||
this.class = newClass;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -10,11 +10,9 @@ import { IconsModule } from '../icons/icons.module';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { AnnotationIconComponent } from './components/annotation-icon/annotation-icon.component';
|
||||
import { SimpleDoughnutChartComponent } from './components/simple-doughnut-chart/simple-doughnut-chart.component';
|
||||
import { HasScrollbarDirective } from './directives/has-scrollbar.directive';
|
||||
import { DictionaryAnnotationIconComponent } from './components/dictionary-annotation-icon/dictionary-annotation-icon.component';
|
||||
import { HiddenActionComponent } from './components/hidden-action/hidden-action.component';
|
||||
import { ConfirmationDialogComponent } from './dialogs/confirmation-dialog/confirmation-dialog.component';
|
||||
import { EmptyStateComponent } from './components/empty-state/empty-state.component';
|
||||
import { CommonUiModule } from '@iqser/common-ui';
|
||||
import { DateAdapter, MAT_DATE_FORMATS, MAT_DATE_LOCALE } from '@angular/material/core';
|
||||
import { MomentDateAdapter } from '@angular/material-moment-adapter';
|
||||
@ -37,7 +35,6 @@ const components = [
|
||||
DictionaryAnnotationIconComponent,
|
||||
HiddenActionComponent,
|
||||
ConfirmationDialogComponent,
|
||||
EmptyStateComponent,
|
||||
SelectComponent,
|
||||
SideNavComponent,
|
||||
DictionaryManagerComponent,
|
||||
@ -47,7 +44,7 @@ const components = [
|
||||
...buttons
|
||||
];
|
||||
|
||||
const utils = [DatePipe, HasScrollbarDirective, NavigateLastDossiersScreenDirective];
|
||||
const utils = [DatePipe, NavigateLastDossiersScreenDirective];
|
||||
|
||||
const modules = [MatConfigModule, ScrollingModule, IconsModule, FormsModule, ReactiveFormsModule, CommonUiModule];
|
||||
|
||||
|
||||
@ -28,7 +28,7 @@ export class PermissionsService {
|
||||
}
|
||||
|
||||
canToggleAnalysis(fileStatus: FileStatusWrapper): boolean {
|
||||
return this._userService.currentUser.isManager && ['UNASSIGNED', 'UNDER_REVIEW', 'UNDER_APPROVAL'].includes(fileStatus.status);
|
||||
return this.isReviewerOrApprover(fileStatus) && ['UNASSIGNED', 'UNDER_REVIEW', 'UNDER_APPROVAL'].includes(fileStatus.status);
|
||||
}
|
||||
|
||||
canReanalyseFile(fileStatus = this._activeFile): boolean {
|
||||
|
||||
@ -1,18 +0,0 @@
|
||||
<svg
|
||||
fill="none"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M14.8285 12.0259L16.2427 13.4402L12 17.6828L7.7574 13.4402L9.17161 12.0259L11 13.8544V6.31724H13V13.8544L14.8285 12.0259Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
clip-rule="evenodd"
|
||||
d="M19.7782 19.7782C15.4824 24.0739 8.51759 24.0739 4.22183 19.7782C-0.0739417 15.4824 -0.0739417 8.51759 4.22183 4.22183C8.51759 -0.0739419 15.4824 -0.0739419 19.7782 4.22183C24.0739 8.51759 24.0739 15.4824 19.7782 19.7782ZM18.364 18.364C14.8492 21.8787 9.15076 21.8787 5.63604 18.364C2.12132 14.8492 2.12132 9.15076 5.63604 5.63604C9.15076 2.12132 14.8492 2.12132 18.364 5.63604C21.8787 9.15076 21.8787 14.8492 18.364 18.364Z"
|
||||
fill="currentColor"
|
||||
fill-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 841 B |
@ -77,6 +77,7 @@
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: fit-content;
|
||||
|
||||
mat-icon {
|
||||
width: 10px;
|
||||
|
||||
@ -66,6 +66,7 @@ section.settings {
|
||||
.actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
|
||||
> *:not(:last-child) {
|
||||
margin-right: 2px;
|
||||
@ -349,6 +350,10 @@ section.settings {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.mr-4 {
|
||||
margin-right: 4px !important;
|
||||
}
|
||||
|
||||
.mr-8 {
|
||||
margin-right: 8px !important;
|
||||
}
|
||||
|
||||
@ -12,121 +12,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
cdk-virtual-scroll-viewport {
|
||||
height: calc(100vh - 50px - 31px - 111px);
|
||||
overflow-y: hidden !important;
|
||||
|
||||
.cdk-virtual-scroll-content-wrapper {
|
||||
display: grid;
|
||||
|
||||
.table-item {
|
||||
display: contents;
|
||||
|
||||
> div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
height: 80px;
|
||||
border-bottom: 1px solid $separator;
|
||||
padding: 0 13px;
|
||||
|
||||
&:not(.scrollbar-placeholder):not(.selection-column) {
|
||||
min-width: 110px;
|
||||
}
|
||||
|
||||
&.selection-column {
|
||||
padding-right: 0 !important;
|
||||
|
||||
iqser-round-checkbox .wrapper {
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s;
|
||||
|
||||
&.active {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.table-item-title {
|
||||
font-weight: 600;
|
||||
@include line-clamp(1);
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
position: absolute;
|
||||
display: none;
|
||||
right: -11px;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
width: fit-content;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding-left: 100px;
|
||||
padding-right: 24px;
|
||||
z-index: 1;
|
||||
background: linear-gradient(to right, rgba(244, 245, 247, 0) 0%, $grey-2 35%);
|
||||
|
||||
mat-icon {
|
||||
width: 14px;
|
||||
}
|
||||
|
||||
iqser-circle-button:not(:last-child) {
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
&.active {
|
||||
display: flex;
|
||||
// compensate for scroll
|
||||
padding-right: 23px;
|
||||
}
|
||||
}
|
||||
|
||||
input,
|
||||
mat-select {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
> div {
|
||||
background-color: $grey-8;
|
||||
|
||||
&.selection-column iqser-round-checkbox .wrapper {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
overflow-y: auto !important;
|
||||
@include scroll-bar;
|
||||
|
||||
&.has-scrollbar {
|
||||
.table-item {
|
||||
.action-buttons {
|
||||
right: 0;
|
||||
padding-right: 13px;
|
||||
}
|
||||
|
||||
.scrollbar-placeholder {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.attributes-actions-container {
|
||||
.table-header-actions {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
|
||||
> *:not(:last-child) {
|
||||
|
||||
@ -1 +1 @@
|
||||
Subproject commit 8b9c96692d5336ae2fdaa843d5adb1d3d9364c47
|
||||
Subproject commit 6c0f123bd97148f8696038f63c9951c241b71990
|
||||
Loading…
x
Reference in New Issue
Block a user