simplify sorting, add common config to base listing

This commit is contained in:
Dan Percic 2021-07-16 02:18:29 +03:00
parent 9d92752655
commit 27de88ac57
42 changed files with 264 additions and 424 deletions

View File

@ -88,7 +88,7 @@
<div <div
(mouseenter)="setHoveredColumn.emit(field.csvColumn)" (mouseenter)="setHoveredColumn.emit(field.csvColumn)"
(mouseleave)="setHoveredColumn.emit()" (mouseleave)="setHoveredColumn.emit()"
*cdkVirtualFor="let field of displayedEntities$ | async" *cdkVirtualFor="let field of displayedEntities$ | async; trackBy: trackByPrimaryKey"
class="table-item" class="table-item"
> >
<div (click)="toggleEntitySelected($event, field)" class="selection-column"> <div (click)="toggleEntitySelected($event, field)" class="selection-column">

View File

@ -20,6 +20,7 @@ export class ActiveFieldsListingComponent extends BaseListingComponent<Field> im
@Output() toggleFieldActive = new EventEmitter<Field>(); @Output() toggleFieldActive = new EventEmitter<Field>();
readonly typeOptions = [FileAttributeConfig.TypeEnum.TEXT, FileAttributeConfig.TypeEnum.NUMBER, FileAttributeConfig.TypeEnum.DATE]; readonly typeOptions = [FileAttributeConfig.TypeEnum.TEXT, FileAttributeConfig.TypeEnum.NUMBER, FileAttributeConfig.TypeEnum.DATE];
protected readonly _primaryKey = 'id';
constructor(protected readonly _injector: Injector) { constructor(protected readonly _injector: Injector) {
super(_injector); super(_injector);

View File

@ -101,7 +101,7 @@
(click)="toggleFieldActive(field)" (click)="toggleFieldActive(field)"
(mouseenter)="setHoveredColumn(field.csvColumn)" (mouseenter)="setHoveredColumn(field.csvColumn)"
(mouseleave)="setHoveredColumn()" (mouseleave)="setHoveredColumn()"
*ngFor="let field of displayedEntities$ | async" *ngFor="let field of displayedEntities$ | async; trackBy: trackByPrimaryKey"
class="csv-header-pill-wrapper" class="csv-header-pill-wrapper"
> >
<div [class.selected]="isActive(field)" class="csv-header-pill"> <div [class.selected]="isActive(field)" class="csv-header-pill">
@ -109,7 +109,10 @@
{{ field.csvColumn }} {{ field.csvColumn }}
</div> </div>
<div class="secondary"> <div class="secondary">
<div class="entry-count small-label">{{ getEntries(field.csvColumn) }} entries</div> <div class="entry-count small-label">
{{ getEntries(field.csvColumn) }}
entries
</div>
<div class="sample small-label">Sample: {{ getSample(field.csvColumn) }}</div> <div class="sample small-label">Sample: {{ getSample(field.csvColumn) }}</div>
</div> </div>
</div> </div>

View File

@ -31,6 +31,8 @@ export interface Field {
providers: [FilterService, SearchService, ScreenStateService, SortingService] providers: [FilterService, SearchService, ScreenStateService, SortingService]
}) })
export class FileAttributesCsvImportDialogComponent extends BaseListingComponent<Field> { export class FileAttributesCsvImportDialogComponent extends BaseListingComponent<Field> {
protected readonly _primaryKey = 'id';
csvFile: File; csvFile: File;
dossierTemplateId: string; dossierTemplateId: string;
parseResult: { data: any[]; errors: any[]; meta: any; fields: Field[] }; parseResult: { data: any[]; errors: any[]; meta: any; fields: Field[] };
@ -50,10 +52,10 @@ export class FileAttributesCsvImportDialogComponent extends BaseListingComponent
private readonly _translateService: TranslateService, private readonly _translateService: TranslateService,
private readonly _toaster: Toaster, private readonly _toaster: Toaster,
private readonly _formBuilder: FormBuilder, private readonly _formBuilder: FormBuilder,
public dialogRef: MatDialogRef<FileAttributesCsvImportDialogComponent>, readonly dialogRef: MatDialogRef<FileAttributesCsvImportDialogComponent>,
protected readonly _injector: Injector, protected readonly _injector: Injector,
@Inject(MAT_DIALOG_DATA) @Inject(MAT_DIALOG_DATA)
public data: { readonly data: {
csv: File; csv: File;
dossierTemplateId: string; dossierTemplateId: string;
existingConfiguration: FileAttributesConfig; existingConfiguration: FileAttributesConfig;

View File

@ -22,10 +22,7 @@
<div class="content-container"> <div class="content-container">
<div class="header-item"> <div class="header-item">
<span class="all-caps-label"> <span class="all-caps-label">
{{ {{ 'audit-screen.table-header.title' | translate: { length: logs?.totalHits || 0 } }}
'audit-screen.table-header.title'
| translate: { length: logs?.totalHits || 0 }
}}
</span> </span>
<div class="actions-wrapper"> <div class="actions-wrapper">
<redaction-pagination <redaction-pagination
@ -37,10 +34,7 @@
<div class="red-input-group w-150 mr-20"> <div class="red-input-group w-150 mr-20">
<mat-form-field class="no-label"> <mat-form-field class="no-label">
<mat-select formControlName="category"> <mat-select formControlName="category">
<mat-option <mat-option *ngFor="let category of categories" [value]="category">
*ngFor="let category of categories"
[value]="category"
>
{{ 'audit-screen.categories.' + category | translate }} {{ 'audit-screen.categories.' + category | translate }}
</mat-option> </mat-option>
</mat-select> </mat-select>
@ -56,10 +50,7 @@
[withName]="true" [withName]="true"
size="small" size="small"
></redaction-initials-avatar> ></redaction-initials-avatar>
<div <div *ngIf="filterForm.get('userId').value === ALL_USERS" [translate]="ALL_USERS"></div>
*ngIf="filterForm.get('userId').value === ALL_USERS"
[translate]="ALL_USERS"
></div>
</mat-select-trigger> </mat-select-trigger>
<mat-option *ngFor="let userId of userIds" [value]="userId"> <mat-option *ngFor="let userId of userIds" [value]="userId">
<redaction-initials-avatar <redaction-initials-avatar
@ -68,26 +59,16 @@
[withName]="true" [withName]="true"
size="small" size="small"
></redaction-initials-avatar> ></redaction-initials-avatar>
<div <div *ngIf="userId === ALL_USERS" [translate]="ALL_USERS"></div>
*ngIf="userId === ALL_USERS"
[translate]="ALL_USERS"
></div>
</mat-option> </mat-option>
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
</div> </div>
<div class="separator">·</div> <div class="separator">·</div>
<div class="red-input-group datepicker-wrapper mr-20"> <div class="red-input-group datepicker-wrapper mr-20">
<input <input [matDatepicker]="fromPicker" formControlName="from" placeholder="dd/mm/yy" />
[matDatepicker]="fromPicker"
formControlName="from"
placeholder="dd/mm/yy"
/>
<mat-datepicker-toggle [for]="fromPicker" matSuffix> <mat-datepicker-toggle [for]="fromPicker" matSuffix>
<mat-icon <mat-icon matDatepickerToggleIcon svgIcon="red:calendar"></mat-icon>
matDatepickerToggleIcon
svgIcon="red:calendar"
></mat-icon>
</mat-datepicker-toggle> </mat-datepicker-toggle>
<mat-datepicker #fromPicker></mat-datepicker> <mat-datepicker #fromPicker></mat-datepicker>
</div> </div>
@ -95,16 +76,9 @@
<div class="mr-20" translate="audit-screen.to"></div> <div class="mr-20" translate="audit-screen.to"></div>
<div class="red-input-group datepicker-wrapper"> <div class="red-input-group datepicker-wrapper">
<input <input [matDatepicker]="toPicker" formControlName="to" placeholder="dd/mm/yy" />
[matDatepicker]="toPicker"
formControlName="to"
placeholder="dd/mm/yy"
/>
<mat-datepicker-toggle [for]="toPicker" matSuffix> <mat-datepicker-toggle [for]="toPicker" matSuffix>
<mat-icon <mat-icon matDatepickerToggleIcon svgIcon="red:calendar"></mat-icon>
matDatepickerToggleIcon
svgIcon="red:calendar"
></mat-icon>
</mat-datepicker-toggle> </mat-datepicker-toggle>
<mat-datepicker #toPicker></mat-datepicker> <mat-datepicker #toPicker></mat-datepicker>
</div> </div>
@ -113,31 +87,18 @@
</div> </div>
<div class="table-header" redactionSyncWidth="table-item"> <div class="table-header" redactionSyncWidth="table-item">
<redaction-table-col-name <redaction-table-col-name column="message" label="audit-screen.table-col-names.message"></redaction-table-col-name>
column="message" <redaction-table-col-name column="date" label="audit-screen.table-col-names.date"></redaction-table-col-name>
label="audit-screen.table-col-names.message"
></redaction-table-col-name>
<redaction-table-col-name
column="date"
label="audit-screen.table-col-names.date"
></redaction-table-col-name>
<redaction-table-col-name <redaction-table-col-name
class="user-column" class="user-column"
column="user" column="user"
label="audit-screen.table-col-names.user" label="audit-screen.table-col-names.user"
></redaction-table-col-name> ></redaction-table-col-name>
<redaction-table-col-name <redaction-table-col-name column="category" label="audit-screen.table-col-names.category"></redaction-table-col-name>
column="category"
label="audit-screen.table-col-names.category"
></redaction-table-col-name>
<div class="scrollbar-placeholder"></div> <div class="scrollbar-placeholder"></div>
</div> </div>
<redaction-empty-state <redaction-empty-state *ngIf="!logs?.totalHits" icon="red:document" screen="audit-screen"></redaction-empty-state>
*ngIf="!logs?.totalHits"
icon="red:document"
screen="audit-screen"
></redaction-empty-state>
<cdk-virtual-scroll-viewport [itemSize]="80" redactionHasScrollbar> <cdk-virtual-scroll-viewport [itemSize]="80" redactionHasScrollbar>
<div *cdkVirtualFor="let log of logs?.data" class="table-item"> <div *cdkVirtualFor="let log of logs?.data" class="table-item">
@ -148,11 +109,7 @@
{{ log.recordDate | date: 'd MMM. yyyy, hh:mm a' }} {{ log.recordDate | date: 'd MMM. yyyy, hh:mm a' }}
</div> </div>
<div class="user-column"> <div class="user-column">
<redaction-initials-avatar <redaction-initials-avatar [userId]="log.userId" [withName]="true" size="small"></redaction-initials-avatar>
[userId]="log.userId"
[withName]="true"
size="small"
></redaction-initials-avatar>
</div> </div>
<div [translate]="'audit-screen.categories.' + log.category"></div> <div [translate]="'audit-screen.categories.' + log.category"></div>
<div class="scrollbar-placeholder"></div> <div class="scrollbar-placeholder"></div>
@ -162,7 +119,3 @@
</div> </div>
</div> </div>
</section> </section>
<redaction-full-page-loading-indicator
[displayed]="!viewReady"
></redaction-full-page-loading-indicator>

View File

@ -1,10 +1,11 @@
import { Component } from '@angular/core'; import { Component, OnDestroy } from '@angular/core';
import { PermissionsService } from '@services/permissions.service'; import { PermissionsService } from '@services/permissions.service';
import { FormBuilder, FormGroup } from '@angular/forms'; import { FormBuilder, FormGroup } from '@angular/forms';
import { AuditControllerService, AuditResponse, AuditSearchRequest } from '@redaction/red-ui-http'; import { AuditControllerService, AuditResponse, AuditSearchRequest } from '@redaction/red-ui-http';
import { TranslateService } from '@ngx-translate/core';
import { Moment } from 'moment'; import { Moment } from 'moment';
import { applyIntervalConstraints } from '@utils/date-inputs-utils'; import { applyIntervalConstraints } from '@utils/date-inputs-utils';
import { LoadingService } from '../../../../services/loading.service';
import { AutoUnsubscribeComponent } from '../../../shared/base/auto-unsubscribe.component';
const PAGE_SIZE = 50; const PAGE_SIZE = 50;
@ -13,16 +14,14 @@ const PAGE_SIZE = 50;
templateUrl: './audit-screen.component.html', templateUrl: './audit-screen.component.html',
styleUrls: ['./audit-screen.component.scss'] styleUrls: ['./audit-screen.component.scss']
}) })
export class AuditScreenComponent { export class AuditScreenComponent extends AutoUnsubscribeComponent implements OnDestroy {
readonly ALL_CATEGORIES = 'all-categories'; readonly ALL_CATEGORIES = 'all-categories';
readonly ALL_USERS = 'audit-screen.all-users'; readonly ALL_USERS = 'audit-screen.all-users';
filterForm: FormGroup; filterForm: FormGroup;
viewReady = false;
categories: string[] = []; categories: string[] = [];
userIds: Set<string>; userIds: Set<string>;
logs: AuditResponse; logs: AuditResponse;
currentPage = 1;
private _previousFrom: Moment; private _previousFrom: Moment;
private _previousTo: Moment; private _previousTo: Moment;
@ -31,8 +30,9 @@ export class AuditScreenComponent {
readonly permissionsService: PermissionsService, readonly permissionsService: PermissionsService,
private readonly _formBuilder: FormBuilder, private readonly _formBuilder: FormBuilder,
private readonly _auditControllerService: AuditControllerService, private readonly _auditControllerService: AuditControllerService,
private readonly _translateService: TranslateService private readonly _loadingService: LoadingService
) { ) {
super();
this.filterForm = this._formBuilder.group({ this.filterForm = this._formBuilder.group({
category: [this.ALL_CATEGORIES], category: [this.ALL_CATEGORIES],
userId: [this.ALL_USERS], userId: [this.ALL_USERS],
@ -40,7 +40,7 @@ export class AuditScreenComponent {
to: [] to: []
}); });
this.filterForm.valueChanges.subscribe(value => { this.addSubscription = this.filterForm.valueChanges.subscribe(value => {
if (!this._updateDateFilters(value)) { if (!this._updateDateFilters(value)) {
this._fetchData(); this._fetchData();
} }
@ -61,16 +61,7 @@ export class AuditScreenComponent {
} }
private _updateDateFilters(value): boolean { private _updateDateFilters(value): boolean {
if ( if (applyIntervalConstraints(value, this._previousFrom, this._previousTo, this.filterForm, 'from', 'to')) {
applyIntervalConstraints(
value,
this._previousFrom,
this._previousTo,
this.filterForm,
'from',
'to'
)
) {
return true; return true;
} }
@ -80,7 +71,7 @@ export class AuditScreenComponent {
} }
private _fetchData(page?: number) { private _fetchData(page?: number) {
this.viewReady = false; this._loadingService.start();
const promises = []; const promises = [];
const category = this.filterForm.get('category').value; const category = this.filterForm.get('category').value;
const userId = this.filterForm.get('userId').value; const userId = this.filterForm.get('userId').value;
@ -110,7 +101,7 @@ export class AuditScreenComponent {
for (const id of this.logs.data.map(log => log.userId).filter(uid => !!uid)) { for (const id of this.logs.data.map(log => log.userId).filter(uid => !!uid)) {
this.userIds.add(id); this.userIds.add(id);
} }
this.viewReady = true; this._loadingService.stop();
}); });
} }
} }

View File

@ -22,14 +22,12 @@
<div class="content-container"> <div class="content-container">
<div class="header-item"> <div class="header-item">
<span class="all-caps-label"> <span class="all-caps-label">
{{ 'default-colors-screen.table-header.title' | translate: { length: screenStateService.allEntitiesLength$ } }} {{ 'default-colors-screen.table-header.title' | translate: { length: screenStateService.allEntitiesLength$ | async } }}
</span> </span>
</div> </div>
<div class="table-header" redactionSyncWidth="table-item"> <div class="table-header" redactionSyncWidth="table-item">
<redaction-table-col-name <redaction-table-col-name
(toggleSort)="toggleSort($event)"
[activeSortingOption]="sortingOption"
[withSort]="true" [withSort]="true"
column="key" column="key"
label="default-colors-screen.table-col-names.key" label="default-colors-screen.table-col-names.key"
@ -46,10 +44,7 @@
<cdk-virtual-scroll-viewport [itemSize]="80" redactionHasScrollbar> <cdk-virtual-scroll-viewport [itemSize]="80" redactionHasScrollbar>
<!-- Table lines --> <!-- Table lines -->
<div <div *cdkVirtualFor="let color of sortedDisplayedEntities$ | async; trackBy: trackByPrimaryKey" class="table-item">
*cdkVirtualFor="let color of screenStateService.allEntities$ | async | sortBy: sortingOption.order:sortingOption.column"
class="table-item"
>
<div> <div>
<div [translate]="'default-colors-screen.types.' + color.key" class="table-item-title heading"></div> <div [translate]="'default-colors-screen.types.' + color.key" class="table-item-title heading"></div>
</div> </div>

View File

@ -1,4 +1,4 @@
import { Component, Injector, OnInit } from '@angular/core'; import { ChangeDetectionStrategy, Component, Injector, OnInit } from '@angular/core';
import { AppStateService } from '@state/app-state.service'; import { AppStateService } from '@state/app-state.service';
import { Colors, DictionaryControllerService } from '@redaction/red-ui-http'; import { Colors, DictionaryControllerService } from '@redaction/red-ui-http';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
@ -8,12 +8,13 @@ import { LoadingService } from '@services/loading.service';
import { FilterService } from '@shared/services/filter.service'; import { FilterService } from '@shared/services/filter.service';
import { SearchService } from '@shared/services/search.service'; import { SearchService } from '@shared/services/search.service';
import { ScreenStateService } from '@shared/services/screen-state.service'; import { ScreenStateService } from '@shared/services/screen-state.service';
import { ScreenNames, SortingService } from '@services/sorting.service';
import { BaseListingComponent } from '@shared/base/base-listing.component'; import { BaseListingComponent } from '@shared/base/base-listing.component';
import { SortingOrders, SortingService } from '../../../../services/sorting.service';
@Component({ @Component({
templateUrl: './default-colors-screen.component.html', templateUrl: './default-colors-screen.component.html',
styleUrls: ['./default-colors-screen.component.scss'], styleUrls: ['./default-colors-screen.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [FilterService, SearchService, ScreenStateService, SortingService] providers: [FilterService, SearchService, ScreenStateService, SortingService]
}) })
export class DefaultColorsScreenComponent export class DefaultColorsScreenComponent
@ -24,6 +25,7 @@ export class DefaultColorsScreenComponent
implements OnInit implements OnInit
{ {
private _colorsObj: Colors; private _colorsObj: Colors;
protected readonly _primaryKey = 'key';
constructor( constructor(
private readonly _appStateService: AppStateService, private readonly _appStateService: AppStateService,
@ -35,7 +37,6 @@ export class DefaultColorsScreenComponent
protected readonly _injector: Injector protected readonly _injector: Injector
) { ) {
super(_injector); super(_injector);
this.sortingService.setScreenName(ScreenNames.DEFAULT_COLORS);
_appStateService.activateDossierTemplate(_activatedRoute.snapshot.params.dossierTemplateId); _appStateService.activateDossierTemplate(_activatedRoute.snapshot.params.dossierTemplateId);
} }
@ -52,9 +53,7 @@ export class DefaultColorsScreenComponent
colorKey: color.key, colorKey: color.key,
dossierTemplateId: this._appStateService.activeDossierTemplateId dossierTemplateId: this._appStateService.activeDossierTemplateId
}, },
async () => { async () => await this._loadColors()
await this._loadColors();
}
); );
} }
@ -62,12 +61,12 @@ export class DefaultColorsScreenComponent
this._loadingService.start(); this._loadingService.start();
const data = await this._dictionaryControllerService.getColors(this._appStateService.activeDossierTemplateId).toPromise(); const data = await this._dictionaryControllerService.getColors(this._appStateService.activeDossierTemplateId).toPromise();
this._colorsObj = data; this._colorsObj = data;
this.screenStateService.setEntities( const entities = Object.keys(data).map(key => ({
Object.keys(data).map(key => ({
key, key,
value: data[key] value: data[key]
})) }));
); this.screenStateService.setEntities(entities);
this.screenStateService.setDisplayedEntities(entities);
this._loadingService.stop(); this._loadingService.stop();
} }
} }

View File

@ -35,7 +35,7 @@
<redaction-circle-button <redaction-circle-button
(action)="openDeleteDictionariesDialog($event)" (action)="openDeleteDictionariesDialog($event)"
*ngIf="canBulkDelete$(permissionsService.isAdmin())" *ngIf="canBulkDelete$(permissionsService.isAdmin()) | async"
icon="red:trash" icon="red:trash"
tooltip="dictionary-listing.bulk.delete" tooltip="dictionary-listing.bulk.delete"
type="dark-bg" type="dark-bg"
@ -63,16 +63,12 @@
<div class="select-oval-placeholder"></div> <div class="select-oval-placeholder"></div>
<redaction-table-col-name <redaction-table-col-name
(toggleSort)="toggleSort($event)"
[activeSortingOption]="sortingOption"
[withSort]="true" [withSort]="true"
column="label" column="label"
label="dictionary-listing.table-col-names.type" label="dictionary-listing.table-col-names.type"
></redaction-table-col-name> ></redaction-table-col-name>
<redaction-table-col-name <redaction-table-col-name
(toggleSort)="toggleSort($event)"
[activeSortingOption]="sortingOption"
[withSort]="true" [withSort]="true"
class="flex-center" class="flex-center"
column="rank" column="rank"
@ -98,7 +94,11 @@
<redaction-empty-state *ngIf="noMatch$ | async" screen="dictionary-listing" type="no-match"></redaction-empty-state> <redaction-empty-state *ngIf="noMatch$ | async" screen="dictionary-listing" type="no-match"></redaction-empty-state>
<cdk-virtual-scroll-viewport [itemSize]="80" redactionHasScrollbar> <cdk-virtual-scroll-viewport [itemSize]="80" redactionHasScrollbar>
<div *cdkVirtualFor="let dict of sortedDisplayedEntities$ | async" [routerLink]="[dict.type]" class="table-item pointer"> <div
*cdkVirtualFor="let dict of sortedDisplayedEntities$ | async; trackBy: trackByPrimaryKey"
[routerLink]="[dict.type]"
class="table-item pointer"
>
<div (click)="toggleEntitySelected($event, dict)" class="selection-column"> <div (click)="toggleEntitySelected($event, dict)" class="selection-column">
<redaction-round-checkbox [active]="isSelected(dict)"></redaction-round-checkbox> <redaction-round-checkbox [active]="isSelected(dict)"></redaction-round-checkbox>
</div> </div>

View File

@ -1,4 +1,4 @@
import { Component, Injector, OnInit } from '@angular/core'; import { ChangeDetectionStrategy, Component, Injector, OnInit } from '@angular/core';
import { DoughnutChartConfig } from '@shared/components/simple-doughnut-chart/simple-doughnut-chart.component'; import { DoughnutChartConfig } from '@shared/components/simple-doughnut-chart/simple-doughnut-chart.component';
import { DictionaryControllerService } from '@redaction/red-ui-http'; import { DictionaryControllerService } from '@redaction/red-ui-http';
import { AppStateService } from '@state/app-state.service'; import { AppStateService } from '@state/app-state.service';
@ -12,7 +12,7 @@ import { LoadingService } from '@services/loading.service';
import { FilterService } from '@shared/services/filter.service'; import { FilterService } from '@shared/services/filter.service';
import { SearchService } from '@shared/services/search.service'; import { SearchService } from '@shared/services/search.service';
import { ScreenStateService } from '@shared/services/screen-state.service'; import { ScreenStateService } from '@shared/services/screen-state.service';
import { ScreenNames, SortingService } from '@services/sorting.service'; import { SortingService } from '../../../../services/sorting.service';
import { BaseListingComponent } from '@shared/base/base-listing.component'; import { BaseListingComponent } from '@shared/base/base-listing.component';
import { AdminDialogService } from '../../services/admin-dialog.service'; import { AdminDialogService } from '../../services/admin-dialog.service';
@ -31,6 +31,8 @@ const toChartConfig = (dict: TypeValueWrapper): DoughnutChartConfig => ({
export class DictionaryListingScreenComponent extends BaseListingComponent<TypeValueWrapper> implements OnInit { export class DictionaryListingScreenComponent extends BaseListingComponent<TypeValueWrapper> implements OnInit {
chartData: DoughnutChartConfig[] = []; chartData: DoughnutChartConfig[] = [];
protected readonly _primaryKey = 'label';
constructor( constructor(
private readonly _dialogService: AdminDialogService, private readonly _dialogService: AdminDialogService,
private readonly _dictionaryControllerService: DictionaryControllerService, private readonly _dictionaryControllerService: DictionaryControllerService,
@ -43,8 +45,6 @@ export class DictionaryListingScreenComponent extends BaseListingComponent<TypeV
) { ) {
super(_injector); super(_injector);
_loadingService.start(); _loadingService.start();
this.sortingService.setScreenName(ScreenNames.DOSSIER_LISTING);
this.searchService.setSearchKey('label');
_appStateService.activateDossierTemplate(_activatedRoute.snapshot.params.dossierTemplateId); _appStateService.activateDossierTemplate(_activatedRoute.snapshot.params.dossierTemplateId);
} }

View File

@ -4,20 +4,10 @@
<redaction-admin-side-nav type="settings"></redaction-admin-side-nav> <redaction-admin-side-nav type="settings"></redaction-admin-side-nav>
<div> <div>
<div class="page-header"> <redaction-page-header
<div class="breadcrumb" translate="digital-signature"></div> [pageLabel]="'digital-signature' | translate"
[showCloseButton]="permissionsService.isUser()"
<div class="actions"> ></redaction-page-header>
<redaction-circle-button
*ngIf="permissionsService.isUser()"
class="ml-6"
icon="red:close"
redactionNavigateLastDossiersScreen
tooltip="common.close"
tooltipPosition="below"
></redaction-circle-button>
</div>
</div>
<div class="red-content-inner"> <div class="red-content-inner">
<div class="content-container"> <div class="content-container">
@ -111,5 +101,3 @@
</div> </div>
</div> </div>
</section> </section>
<redaction-full-page-loading-indicator [displayed]="!viewReady"></redaction-full-page-loading-indicator>

View File

@ -5,6 +5,7 @@ import { Toaster } from '../../../../services/toaster.service';
import { PermissionsService } from '@services/permissions.service'; import { PermissionsService } from '@services/permissions.service';
import { lastIndexOfEnd } from '@utils/functions'; import { lastIndexOfEnd } from '@utils/functions';
import { AutoUnsubscribeComponent } from '../../../shared/base/auto-unsubscribe.component'; import { AutoUnsubscribeComponent } from '../../../shared/base/auto-unsubscribe.component';
import { LoadingService } from '../../../../services/loading.service';
@Component({ @Component({
selector: 'redaction-digital-signature-screen', selector: 'redaction-digital-signature-screen',
@ -15,13 +16,13 @@ export class DigitalSignatureScreenComponent extends AutoUnsubscribeComponent im
digitalSignature: DigitalSignature; digitalSignature: DigitalSignature;
digitalSignatureForm: FormGroup; digitalSignatureForm: FormGroup;
viewReady = false;
digitalSignatureExists = false; digitalSignatureExists = false;
constructor( constructor(
private readonly _digitalSignatureControllerService: DigitalSignatureControllerService, private readonly _digitalSignatureControllerService: DigitalSignatureControllerService,
private readonly _toaster: Toaster, private readonly _toaster: Toaster,
private readonly _formBuilder: FormBuilder, private readonly _formBuilder: FormBuilder,
private readonly _loadingService: LoadingService,
readonly permissionsService: PermissionsService readonly permissionsService: PermissionsService
) { ) {
super(); super();
@ -82,7 +83,7 @@ export class DigitalSignatureScreenComponent extends AutoUnsubscribeComponent im
} }
loadDigitalSignatureAndInitializeForm() { loadDigitalSignatureAndInitializeForm() {
this.viewReady = false; this._loadingService.start();
this.addSubscription = this._digitalSignatureControllerService this.addSubscription = this._digitalSignatureControllerService
.getDigitalSignature() .getDigitalSignature()
.subscribe( .subscribe(
@ -97,7 +98,7 @@ export class DigitalSignatureScreenComponent extends AutoUnsubscribeComponent im
) )
.add(() => { .add(() => {
this._initForm(); this._initForm();
this.viewReady = true; this._loadingService.stop();
}); });
} }

View File

@ -65,16 +65,12 @@
<div class="select-oval-placeholder"></div> <div class="select-oval-placeholder"></div>
<redaction-table-col-name <redaction-table-col-name
(toggleSort)="toggleSort($event)"
[activeSortingOption]="sortingOption"
[withSort]="true" [withSort]="true"
column="label" column="label"
label="dossier-attributes-listing.table-col-names.label" label="dossier-attributes-listing.table-col-names.label"
></redaction-table-col-name> ></redaction-table-col-name>
<redaction-table-col-name label="dossier-attributes-listing.table-col-names.placeholder"></redaction-table-col-name> <redaction-table-col-name label="dossier-attributes-listing.table-col-names.placeholder"></redaction-table-col-name>
<redaction-table-col-name <redaction-table-col-name
(toggleSort)="toggleSort($event)"
[activeSortingOption]="sortingOption"
[withSort]="true" [withSort]="true"
column="type" column="type"
label="dossier-attributes-listing.table-col-names.type" label="dossier-attributes-listing.table-col-names.type"
@ -94,7 +90,10 @@
<redaction-empty-state *ngIf="noMatch$ | async" screen="dossier-attributes-listing" type="no-match"></redaction-empty-state> <redaction-empty-state *ngIf="noMatch$ | async" screen="dossier-attributes-listing" type="no-match"></redaction-empty-state>
<cdk-virtual-scroll-viewport [itemSize]="50" redactionHasScrollbar> <cdk-virtual-scroll-viewport [itemSize]="50" redactionHasScrollbar>
<div *cdkVirtualFor="let attribute of sortedDisplayedEntities$ | async" class="table-item pointer"> <div
*cdkVirtualFor="let attribute of sortedDisplayedEntities$ | async; trackBy: trackByPrimaryKey"
class="table-item pointer"
>
<div (click)="toggleEntitySelected($event, attribute)" class="selection-column"> <div (click)="toggleEntitySelected($event, attribute)" class="selection-column">
<redaction-round-checkbox [active]="isSelected(attribute)"></redaction-round-checkbox> <redaction-round-checkbox [active]="isSelected(attribute)"></redaction-round-checkbox>
</div> </div>

View File

@ -5,7 +5,7 @@ import { AppStateService } from '@state/app-state.service';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { AdminDialogService } from '../../services/admin-dialog.service'; import { AdminDialogService } from '../../services/admin-dialog.service';
import { LoadingService } from '@services/loading.service'; import { LoadingService } from '@services/loading.service';
import { ScreenNames, SortingService } from '@services/sorting.service'; import { SortingService } from '../../../../services/sorting.service';
import { FilterService } from '@shared/services/filter.service'; import { FilterService } from '@shared/services/filter.service';
import { SearchService } from '@shared/services/search.service'; import { SearchService } from '@shared/services/search.service';
import { ScreenStateService } from '@shared/services/screen-state.service'; import { ScreenStateService } from '@shared/services/screen-state.service';
@ -18,6 +18,8 @@ import { DossierAttributesService } from '@shared/services/controller-wrappers/d
providers: [FilterService, SearchService, ScreenStateService, SortingService] providers: [FilterService, SearchService, ScreenStateService, SortingService]
}) })
export class DossierAttributesListingScreenComponent extends BaseListingComponent<DossierAttributeConfig> implements OnInit { export class DossierAttributesListingScreenComponent extends BaseListingComponent<DossierAttributeConfig> implements OnInit {
protected readonly _primaryKey = 'label';
constructor( constructor(
protected readonly _injector: Injector, protected readonly _injector: Injector,
private readonly _appStateService: AppStateService, private readonly _appStateService: AppStateService,
@ -28,8 +30,6 @@ export class DossierAttributesListingScreenComponent extends BaseListingComponen
readonly permissionsService: PermissionsService readonly permissionsService: PermissionsService
) { ) {
super(_injector); super(_injector);
this.searchService.setSearchKey('label');
this.sortingService.setScreenName(ScreenNames.DOSSIER_ATTRIBUTES_LISTING);
_appStateService.activateDossierTemplate(_activatedRoute.snapshot.params.dossierTemplateId); _appStateService.activateDossierTemplate(_activatedRoute.snapshot.params.dossierTemplateId);
} }

View File

@ -4,17 +4,10 @@
<redaction-admin-side-nav type="settings"></redaction-admin-side-nav> <redaction-admin-side-nav type="settings"></redaction-admin-side-nav>
<div> <div>
<div class="page-header"> <redaction-page-header
<div class="breadcrumb" translate="dossier-templates"></div> [pageLabel]="'dossier-templates' | translate"
[showCloseButton]="permissionsService.isUser()"
<redaction-circle-button ></redaction-page-header>
*ngIf="permissionsService.isUser()"
icon="red:close"
redactionNavigateLastDossiersScreen
tooltip="common.close"
tooltipPosition="below"
></redaction-circle-button>
</div>
<div class="red-content-inner"> <div class="red-content-inner">
<div class="content-container"> <div class="content-container">
@ -34,15 +27,13 @@
}} }}
</span> </span>
<ng-container *ngIf="screenStateService.areSomeEntitiesSelected$ | async">
<redaction-circle-button <redaction-circle-button
(action)="openDeleteTemplatesDialog($event)" (action)="openDeleteTemplatesDialog($event)"
*ngIf="permissionsService.isAdmin()" *ngIf="canBulkDelete$(permissionsService.isAdmin()) | async"
icon="red:trash" icon="red:trash"
tooltip="dossier-templates-listing.bulk.delete" tooltip="dossier-templates-listing.bulk.delete"
type="dark-bg" type="dark-bg"
></redaction-circle-button> ></redaction-circle-button>
</ng-container>
<div class="actions flex-1"> <div class="actions flex-1">
<redaction-input-with-action <redaction-input-with-action
@ -65,8 +56,6 @@
<div class="select-oval-placeholder"></div> <div class="select-oval-placeholder"></div>
<redaction-table-col-name <redaction-table-col-name
(toggleSort)="toggleSort($event)"
[activeSortingOption]="sortingOption"
[withSort]="true" [withSort]="true"
column="name" column="name"
label="dossier-templates-listing.table-col-names.name" label="dossier-templates-listing.table-col-names.name"
@ -76,19 +65,16 @@
label="dossier-templates-listing.table-col-names.created-by" label="dossier-templates-listing.table-col-names.created-by"
></redaction-table-col-name> ></redaction-table-col-name>
<redaction-table-col-name <redaction-table-col-name
(toggleSort)="toggleSort($event)"
[activeSortingOption]="sortingOption"
[withSort]="true" [withSort]="true"
column="dateAdded" column="dateAdded"
label="dossier-templates-listing.table-col-names.created-on" label="dossier-templates-listing.table-col-names.created-on"
></redaction-table-col-name> ></redaction-table-col-name>
<redaction-table-col-name <redaction-table-col-name
(toggleSort)="toggleSort($event)"
[activeSortingOption]="sortingOption"
[withSort]="true" [withSort]="true"
column="dateModified" column="dateModified"
label="dossier-templates-listing.table-col-names.modified-on" label="dossier-templates-listing.table-col-names.modified-on"
></redaction-table-col-name> ></redaction-table-col-name>
<div class="scrollbar-placeholder"></div> <div class="scrollbar-placeholder"></div>
</div> </div>

View File

@ -9,8 +9,8 @@ import { DossierTemplateControllerService } from '@redaction/red-ui-http';
import { FilterService } from '@shared/services/filter.service'; import { FilterService } from '@shared/services/filter.service';
import { SearchService } from '@shared/services/search.service'; import { SearchService } from '@shared/services/search.service';
import { ScreenStateService } from '@shared/services/screen-state.service'; import { ScreenStateService } from '@shared/services/screen-state.service';
import { ScreenNames, SortingService } from '@services/sorting.service';
import { BaseListingComponent } from '@shared/base/base-listing.component'; import { BaseListingComponent } from '@shared/base/base-listing.component';
import { SortingService } from '../../../../services/sorting.service';
@Component({ @Component({
templateUrl: './dossier-templates-listing-screen.component.html', templateUrl: './dossier-templates-listing-screen.component.html',
@ -19,6 +19,8 @@ import { BaseListingComponent } from '@shared/base/base-listing.component';
providers: [FilterService, SearchService, ScreenStateService, SortingService] providers: [FilterService, SearchService, ScreenStateService, SortingService]
}) })
export class DossierTemplatesListingScreenComponent extends BaseListingComponent<DossierTemplateModelWrapper> implements OnInit { export class DossierTemplatesListingScreenComponent extends BaseListingComponent<DossierTemplateModelWrapper> implements OnInit {
protected _primaryKey = 'name';
constructor( constructor(
private readonly _dialogService: AdminDialogService, private readonly _dialogService: AdminDialogService,
private readonly _appStateService: AppStateService, private readonly _appStateService: AppStateService,
@ -29,8 +31,6 @@ export class DossierTemplatesListingScreenComponent extends BaseListingComponent
readonly userPreferenceService: UserPreferenceService readonly userPreferenceService: UserPreferenceService
) { ) {
super(_injector); super(_injector);
this.sortingService.setScreenName(ScreenNames.DOSSIER_TEMPLATES_LISTING);
this.searchService.setSearchKey('name');
} }
ngOnInit(): void { ngOnInit(): void {

View File

@ -37,7 +37,7 @@
<redaction-circle-button <redaction-circle-button
(click)="openConfirmDeleteAttributeDialog($event)" (click)="openConfirmDeleteAttributeDialog($event)"
*ngIf="permissionsService.isAdmin() && screenStateService.areSomeEntitiesSelected$ | async" *ngIf="canBulkDelete$(permissionsService.isAdmin()) | async"
icon="red:trash" icon="red:trash"
tooltip="file-attributes-listing.bulk-actions.delete" tooltip="file-attributes-listing.bulk-actions.delete"
type="dark-bg" type="dark-bg"
@ -74,24 +74,18 @@
<div class="select-oval-placeholder"></div> <div class="select-oval-placeholder"></div>
<redaction-table-col-name <redaction-table-col-name
(toggleSort)="toggleSort($event)"
[activeSortingOption]="sortingOption"
[withSort]="true" [withSort]="true"
column="label" column="label"
label="file-attributes-listing.table-col-names.name" label="file-attributes-listing.table-col-names.name"
></redaction-table-col-name> ></redaction-table-col-name>
<redaction-table-col-name <redaction-table-col-name
(toggleSort)="toggleSort($event)"
[activeSortingOption]="sortingOption"
[withSort]="true" [withSort]="true"
column="type" column="type"
label="file-attributes-listing.table-col-names.type" label="file-attributes-listing.table-col-names.type"
></redaction-table-col-name> ></redaction-table-col-name>
<redaction-table-col-name <redaction-table-col-name
(toggleSort)="toggleSort($event)"
[activeSortingOption]="sortingOption"
[withSort]="true" [withSort]="true"
class="flex-center" class="flex-center"
column="editable" column="editable"

View File

@ -1,4 +1,4 @@
import { ChangeDetectionStrategy, Component, ElementRef, Injector, OnInit, ViewChild } from '@angular/core'; import { ChangeDetectionStrategy, Component, ElementRef, Injector, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { PermissionsService } from '@services/permissions.service'; import { PermissionsService } from '@services/permissions.service';
import { FileAttributeConfig, FileAttributesConfig, FileAttributesControllerService } from '@redaction/red-ui-http'; import { FileAttributeConfig, FileAttributesConfig, FileAttributesControllerService } from '@redaction/red-ui-http';
import { AppStateService } from '@state/app-state.service'; import { AppStateService } from '@state/app-state.service';
@ -8,7 +8,7 @@ import { LoadingService } from '@services/loading.service';
import { FilterService } from '@shared/services/filter.service'; import { FilterService } from '@shared/services/filter.service';
import { SearchService } from '@shared/services/search.service'; import { SearchService } from '@shared/services/search.service';
import { ScreenStateService } from '@shared/services/screen-state.service'; import { ScreenStateService } from '@shared/services/screen-state.service';
import { ScreenNames, SortingService } from '@services/sorting.service'; import { SortingService } from '../../../../services/sorting.service';
import { BaseListingComponent } from '@shared/base/base-listing.component'; import { BaseListingComponent } from '@shared/base/base-listing.component';
@Component({ @Component({
@ -17,7 +17,9 @@ import { BaseListingComponent } from '@shared/base/base-listing.component';
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
providers: [FilterService, SearchService, ScreenStateService, SortingService] providers: [FilterService, SearchService, ScreenStateService, SortingService]
}) })
export class FileAttributesListingScreenComponent extends BaseListingComponent<FileAttributeConfig> implements OnInit { export class FileAttributesListingScreenComponent extends BaseListingComponent<FileAttributeConfig> implements OnInit, OnDestroy {
protected readonly _primaryKey = 'label';
private _existingConfiguration: FileAttributesConfig; private _existingConfiguration: FileAttributesConfig;
@ViewChild('fileInput') private _fileInput: ElementRef; @ViewChild('fileInput') private _fileInput: ElementRef;
@ -31,8 +33,6 @@ export class FileAttributesListingScreenComponent extends BaseListingComponent<F
protected readonly _injector: Injector protected readonly _injector: Injector
) { ) {
super(_injector); super(_injector);
this.sortingService.setScreenName(ScreenNames.FILE_ATTRIBUTES_LISTING);
this.searchService.setSearchKey('label');
_appStateService.activateDossierTemplate(_activatedRoute.snapshot.params.dossierTemplateId); _appStateService.activateDossierTemplate(_activatedRoute.snapshot.params.dossierTemplateId);
} }

View File

@ -3,27 +3,12 @@
<redaction-admin-side-nav type="settings"></redaction-admin-side-nav> <redaction-admin-side-nav type="settings"></redaction-admin-side-nav>
<div *ngIf="viewReady"> <div>
<div class="page-header"> <redaction-page-header
<div class="breadcrumb" translate="license-information"></div> [pageLabel]="'license-information' | translate"
[showCloseButton]="permissionsService.isUser()"
<div class="actions"> [buttonConfigs]="buttonConfigs"
<button ></redaction-page-header>
(click)="sendMail()"
color="primary"
mat-flat-button
translate="license-info-screen.email-report"
></button>
<redaction-circle-button
*ngIf="permissionsService.isUser()"
class="ml-6"
icon="red:close"
redactionNavigateLastDossiersScreen
tooltip="common.close"
tooltipPosition="below"
></redaction-circle-button>
</div>
</div>
<div class="red-content-inner"> <div class="red-content-inner">
<div class="content-container"> <div class="content-container">
@ -46,10 +31,7 @@
<div class="row"> <div class="row">
<div translate="license-info-screen.copyright-claim-title"></div> <div translate="license-info-screen.copyright-claim-title"></div>
<div> <div>
{{ {{ 'license-info-screen.copyright-claim-text' | translate: { currentYear: currentYear } }}
'license-info-screen.copyright-claim-text'
| translate: { currentYear: currentYear }
}}
</div> </div>
</div> </div>
@ -64,10 +46,7 @@
<!-- <div>Future feature: we will provide that with the /info endpoint</div>--> <!-- <div>Future feature: we will provide that with the /info endpoint</div>-->
<!-- </div>--> <!-- </div>-->
<div <div class="section-title all-caps-label" translate="license-info-screen.licensing-details"></div>
class="section-title all-caps-label"
translate="license-info-screen.licensing-details"
></div>
<div class="row"> <div class="row">
<div translate="license-info-screen.licensed-to"></div> <div translate="license-info-screen.licensed-to"></div>
@ -92,28 +71,18 @@
<div>{{ currentInfo.numberOfAnalyzedPages }}</div> <div>{{ currentInfo.numberOfAnalyzedPages }}</div>
</div> </div>
<div <div class="section-title all-caps-label" translate="license-info-screen.usage-details"></div>
class="section-title all-caps-label"
translate="license-info-screen.usage-details"
></div>
<div class="row"> <div class="row">
<div> <div>
{{ {{ 'license-info-screen.total-analyzed' | translate: { date: totalInfo.startDate | date: 'longDate' } }}
'license-info-screen.total-analyzed'
| translate: { date: totalInfo.startDate | date: 'longDate' }
}}
</div> </div>
<div>{{ totalInfo.numberOfAnalyzedPages }}</div> <div>{{ totalInfo.numberOfAnalyzedPages }}</div>
</div> </div>
<div class="row"> <div class="row">
<div translate="license-info-screen.current-analyzed"></div> <div translate="license-info-screen.current-analyzed"></div>
<div> <div>{{ currentInfo.numberOfAnalyzedPages }} ({{ analysisPercentageOfLicense | number: '1.0-2' }}%)</div>
{{ currentInfo.numberOfAnalyzedPages }} ({{
analysisPercentageOfLicense | number: '1.0-2'
}}%)
</div>
</div> </div>
<div *ngIf="!!unlicensedInfo" class="row"> <div *ngIf="!!unlicensedInfo" class="row">
@ -150,7 +119,3 @@
</div> </div>
</div> </div>
</section> </section>
<redaction-full-page-loading-indicator
[displayed]="!viewReady"
></redaction-full-page-loading-indicator>

View File

@ -4,6 +4,9 @@ import { LicenseReport, LicenseReportControllerService } from '@redaction/red-ui
import { AppConfigService } from '@app-config/app-config.service'; import { AppConfigService } from '@app-config/app-config.service';
import * as moment from 'moment'; import * as moment from 'moment';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { LoadingService } from '../../../../services/loading.service';
import { ButtonConfig } from '../../../shared/components/page-header/models/button-config.model';
import { IconButtonTypes } from '../../../shared/components/buttons/icon-button/icon-button.component';
@Component({ @Component({
selector: 'redaction-license-information-screen', selector: 'redaction-license-information-screen',
@ -16,7 +19,6 @@ export class LicenseInformationScreenComponent implements OnInit {
unlicensedInfo: LicenseReport = {}; unlicensedInfo: LicenseReport = {};
totalLicensedNumberOfPages = 0; totalLicensedNumberOfPages = 0;
analysisPercentageOfLicense = 100; analysisPercentageOfLicense = 100;
viewReady = false;
barChart: any[] = []; barChart: any[] = [];
lineChartSeries: any[] = []; lineChartSeries: any[] = [];
yAxisLabel = this._translateService.instant('license-info-screen.chart.pages-per-month'); yAxisLabel = this._translateService.instant('license-info-screen.chart.pages-per-month');
@ -31,13 +33,23 @@ export class LicenseInformationScreenComponent implements OnInit {
group: 'Ordinal', group: 'Ordinal',
domain: ['#0389ec'] domain: ['#0389ec']
}; };
buttonConfigs: ButtonConfig[] = [
{
label: this._translateService.instant('license-info-screen.email-report'),
action: () => this.sendMail(),
type: IconButtonTypes.PRIMARY
}
];
constructor( constructor(
readonly permissionsService: PermissionsService, readonly permissionsService: PermissionsService,
readonly appConfigService: AppConfigService, readonly appConfigService: AppConfigService,
private readonly _licenseReportController: LicenseReportControllerService, private readonly _licenseReportController: LicenseReportControllerService,
private readonly _translateService: TranslateService private readonly _translateService: TranslateService,
) {} private readonly _loadingService: LoadingService
) {
_loadingService.start();
}
get currentYear(): number { get currentYear(): number {
return new Date().getFullYear(); return new Date().getFullYear();
@ -63,18 +75,15 @@ export class LicenseInformationScreenComponent implements OnInit {
const unlicensedConfig = { const unlicensedConfig = {
startDate: endDate.toDate() startDate: endDate.toDate()
}; };
promises.push( promises.push(this._licenseReportController.licenseReport(unlicensedConfig).toPromise());
this._licenseReportController.licenseReport(unlicensedConfig).toPromise()
);
} }
Promise.all(promises).then(reports => { Promise.all(promises).then(reports => {
[this.currentInfo, this.totalInfo, this.unlicensedInfo] = reports; [this.currentInfo, this.totalInfo, this.unlicensedInfo] = reports;
this.viewReady = true; this._loadingService.stop();
this.analysisPercentageOfLicense = this.analysisPercentageOfLicense =
this.totalLicensedNumberOfPages > 0 this.totalLicensedNumberOfPages > 0
? (this.currentInfo.numberOfAnalyzedPages / this.totalLicensedNumberOfPages) * ? (this.currentInfo.numberOfAnalyzedPages / this.totalLicensedNumberOfPages) * 100
100
: 100; : 100;
}); });
} }
@ -92,9 +101,7 @@ export class LicenseInformationScreenComponent implements OnInit {
pages: this.totalLicensedNumberOfPages pages: this.totalLicensedNumberOfPages
}) })
].join('%0D%0A'); ].join('%0D%0A');
window.location.href = `mailto:${this.appConfigService.getConfig( window.location.href = `mailto:${this.appConfigService.getConfig('LICENSE_EMAIL')}?subject=${subject}&body=${body}`;
'LICENSE_EMAIL'
)}?subject=${subject}&body=${body}`;
} }
private async _setMonthlyStats(startDate: moment.Moment, endDate: moment.Moment) { private async _setMonthlyStats(startDate: moment.Moment, endDate: moment.Moment) {

View File

@ -39,8 +39,6 @@
<div class="select-oval-placeholder"></div> <div class="select-oval-placeholder"></div>
<redaction-table-col-name <redaction-table-col-name
(toggleSort)="toggleSort($event)"
[activeSortingOption]="sortingOption"
[label]="'trash.table-col-names.name' | translate" [label]="'trash.table-col-names.name' | translate"
[withSort]="true" [withSort]="true"
column="name" column="name"
@ -50,15 +48,11 @@
class="user-column" class="user-column"
></redaction-table-col-name> ></redaction-table-col-name>
<redaction-table-col-name <redaction-table-col-name
(toggleSort)="toggleSort($event)"
[activeSortingOption]="sortingOption"
[label]="'trash.table-col-names.deleted-on' | translate" [label]="'trash.table-col-names.deleted-on' | translate"
[withSort]="true" [withSort]="true"
column="dateDeleted" column="dateDeleted"
></redaction-table-col-name> ></redaction-table-col-name>
<redaction-table-col-name <redaction-table-col-name
(toggleSort)="toggleSort($event)"
[activeSortingOption]="sortingOption"
[label]="'trash.table-col-names.time-to-restore' | translate" [label]="'trash.table-col-names.time-to-restore' | translate"
[withSort]="true" [withSort]="true"
column="timeToRestore" column="timeToRestore"
@ -71,7 +65,7 @@
<redaction-empty-state *ngIf="noMatch$ | async" screen="trash" type="no-match"></redaction-empty-state> <redaction-empty-state *ngIf="noMatch$ | async" screen="trash" type="no-match"></redaction-empty-state>
<cdk-virtual-scroll-viewport [itemSize]="itemSize" redactionHasScrollbar> <cdk-virtual-scroll-viewport [itemSize]="itemSize" redactionHasScrollbar>
<div *cdkVirtualFor="let entity of sortedDisplayedEntities$ | async; trackBy: trackById" class="table-item"> <div *cdkVirtualFor="let entity of sortedDisplayedEntities$ | async; trackBy: trackByPrimaryKey" class="table-item">
<div (click)="toggleEntitySelected($event, entity)" class="selection-column"> <div (click)="toggleEntitySelected($event, entity)" class="selection-column">
<redaction-round-checkbox [active]="isSelected(entity)"></redaction-round-checkbox> <redaction-round-checkbox [active]="isSelected(entity)"></redaction-round-checkbox>
</div> </div>

View File

@ -8,7 +8,7 @@ import * as moment from 'moment';
import { FilterService } from '@shared/services/filter.service'; import { FilterService } from '@shared/services/filter.service';
import { SearchService } from '@shared/services/search.service'; import { SearchService } from '@shared/services/search.service';
import { ScreenStateService } from '@shared/services/screen-state.service'; import { ScreenStateService } from '@shared/services/screen-state.service';
import { ScreenNames, SortingService } from '@services/sorting.service'; import { SortingService } from '../../../../services/sorting.service';
import { BaseListingComponent } from '@shared/base/base-listing.component'; import { BaseListingComponent } from '@shared/base/base-listing.component';
import { DossiersService } from '../../../dossier/services/dossiers.service'; import { DossiersService } from '../../../dossier/services/dossiers.service';
@ -21,6 +21,7 @@ import { DossiersService } from '../../../dossier/services/dossiers.service';
export class TrashScreenComponent extends BaseListingComponent<Dossier> implements OnInit { export class TrashScreenComponent extends BaseListingComponent<Dossier> implements OnInit {
readonly itemSize = 85; readonly itemSize = 85;
private readonly _deleteRetentionHours = this._appConfigService.getConfig(AppConfigKey.DELETE_RETENTION_HOURS); private readonly _deleteRetentionHours = this._appConfigService.getConfig(AppConfigKey.DELETE_RETENTION_HOURS);
protected readonly _primaryKey = 'dossierName';
constructor( constructor(
private readonly _appStateService: AppStateService, private readonly _appStateService: AppStateService,
@ -32,7 +33,6 @@ export class TrashScreenComponent extends BaseListingComponent<Dossier> implemen
private readonly _appConfigService: AppConfigService private readonly _appConfigService: AppConfigService
) { ) {
super(_injector); super(_injector);
this.sortingService.setScreenName(ScreenNames.DOSSIER_LISTING);
} }
async ngOnInit(): Promise<void> { async ngOnInit(): Promise<void> {
@ -52,10 +52,6 @@ export class TrashScreenComponent extends BaseListingComponent<Dossier> implemen
return moment(softDeletedTime).add(this._deleteRetentionHours, 'hours').toISOString(); return moment(softDeletedTime).add(this._deleteRetentionHours, 'hours').toISOString();
} }
trackById(index: number, dossier: Dossier): string {
return dossier.dossierId;
}
bulkDelete(dossierIds = this.screenStateService.selectedEntities.map(d => d.dossierId)) { bulkDelete(dossierIds = this.screenStateService.selectedEntities.map(d => d.dossierId)) {
this._loadingService.loadWhile(this._hardDelete(dossierIds)); this._loadingService.loadWhile(this._hardDelete(dossierIds));
} }

View File

@ -76,7 +76,7 @@
<cdk-virtual-scroll-viewport [itemSize]="80" redactionHasScrollbar> <cdk-virtual-scroll-viewport [itemSize]="80" redactionHasScrollbar>
<!-- Table lines --> <!-- Table lines -->
<div *cdkVirtualFor="let user of displayedEntities$ | async; trackBy: trackById" class="table-item"> <div *cdkVirtualFor="let user of displayedEntities$ | async; trackBy: trackByPrimaryKey" class="table-item">
<div (click)="toggleEntitySelected($event, user)" class="selection-column"> <div (click)="toggleEntitySelected($event, user)" class="selection-column">
<redaction-round-checkbox [active]="isSelected(user)"></redaction-round-checkbox> <redaction-round-checkbox [active]="isSelected(user)"></redaction-round-checkbox>
</div> </div>

View File

@ -23,6 +23,8 @@ import { map } from 'rxjs/operators';
providers: [FilterService, SearchService, ScreenStateService, SortingService] providers: [FilterService, SearchService, ScreenStateService, SortingService]
}) })
export class UserListingScreenComponent extends BaseListingComponent<User> implements OnInit { export class UserListingScreenComponent extends BaseListingComponent<User> implements OnInit {
protected readonly _primaryKey = 'userId';
collapsedDetails = false; collapsedDetails = false;
chartData: DoughnutChartConfig[] = []; chartData: DoughnutChartConfig[] = [];
@ViewChildren(InitialsAvatarComponent) @ViewChildren(InitialsAvatarComponent)
@ -81,10 +83,6 @@ export class UserListingScreenComponent extends BaseListingComponent<User> imple
this.openDeleteUsersDialog(this.screenStateService.allEntities.filter(u => this.isSelected(u))); this.openDeleteUsersDialog(this.screenStateService.allEntities.filter(u => this.isSelected(u)));
} }
trackById(index: number, user: User) {
return user.userId;
}
private async _loadData() { private async _loadData() {
this.screenStateService.setEntities(await this._userControllerService.getAllUsers().toPromise()); this.screenStateService.setEntities(await this._userControllerService.getAllUsers().toPromise());
await this.userService.loadAllUsers(); await this.userService.loadAllUsers();

View File

@ -21,8 +21,7 @@
icon="red:assign-me" icon="red:assign-me"
tooltip="dossier-overview.assign-me" tooltip="dossier-overview.assign-me"
type="dark-bg" type="dark-bg"
> ></redaction-circle-button>
</redaction-circle-button>
<redaction-circle-button <redaction-circle-button
(action)="setToUnderApproval()" (action)="setToUnderApproval()"
@ -30,8 +29,7 @@
icon="red:ready-for-approval" icon="red:ready-for-approval"
tooltip="dossier-overview.under-approval" tooltip="dossier-overview.under-approval"
type="dark-bg" type="dark-bg"
> ></redaction-circle-button>
</redaction-circle-button>
<redaction-circle-button <redaction-circle-button
(action)="setToUnderReview()" (action)="setToUnderReview()"
@ -39,13 +37,9 @@
icon="red:undo" icon="red:undo"
tooltip="dossier-overview.under-review" tooltip="dossier-overview.under-review"
type="dark-bg" type="dark-bg"
> ></redaction-circle-button>
</redaction-circle-button>
<redaction-file-download-btn <redaction-file-download-btn [dossier]="dossier" [file]="selectedFiles"></redaction-file-download-btn>
[dossier]="dossier"
[file]="selectedFiles"
></redaction-file-download-btn>
<!-- Approved--> <!-- Approved-->
<redaction-circle-button <redaction-circle-button
@ -55,8 +49,7 @@
[tooltip]="canApprove ? 'dossier-overview.approve' : 'dossier-overview.approve-disabled'" [tooltip]="canApprove ? 'dossier-overview.approve' : 'dossier-overview.approve-disabled'"
icon="red:approved" icon="red:approved"
type="dark-bg" type="dark-bg"
> ></redaction-circle-button>
</redaction-circle-button>
<!-- Back to approval --> <!-- Back to approval -->
<redaction-circle-button <redaction-circle-button
@ -65,8 +58,7 @@
icon="red:undo" icon="red:undo"
tooltip="dossier-overview.under-approval" tooltip="dossier-overview.under-approval"
type="dark-bg" type="dark-bg"
> ></redaction-circle-button>
</redaction-circle-button>
<redaction-circle-button <redaction-circle-button
(action)="ocr()" (action)="ocr()"

View File

@ -1,12 +1,10 @@
import { ChangeDetectorRef, Component, EventEmitter, Output } from '@angular/core'; import { Component, EventEmitter, Output } from '@angular/core';
import { AppStateService } from '@state/app-state.service'; import { AppStateService } from '@state/app-state.service';
import { UserService } from '@services/user.service';
import { FileManagementControllerService, ReanalysisControllerService } from '@redaction/red-ui-http'; import { FileManagementControllerService, ReanalysisControllerService } from '@redaction/red-ui-http';
import { PermissionsService } from '@services/permissions.service'; import { PermissionsService } from '@services/permissions.service';
import { FileStatusWrapper } from '@models/file/file-status.wrapper'; import { FileStatusWrapper } from '@models/file/file-status.wrapper';
import { FileActionService } from '../../services/file-action.service'; import { FileActionService } from '../../services/file-action.service';
import { from, Observable } from 'rxjs'; import { from, Observable } from 'rxjs';
import { StatusOverlayService } from '@upload-download/services/status-overlay.service';
import { DossiersDialogService } from '../../services/dossiers-dialog.service'; import { DossiersDialogService } from '../../services/dossiers-dialog.service';
import { LoadingService } from '../../../../services/loading.service'; import { LoadingService } from '../../../../services/loading.service';
import { ConfirmationDialogInput } from '../../../shared/dialogs/confirmation-dialog/confirmation-dialog.component'; import { ConfirmationDialogInput } from '../../../shared/dialogs/confirmation-dialog/confirmation-dialog.component';
@ -23,14 +21,11 @@ export class DossierOverviewBulkActionsComponent {
constructor( constructor(
private readonly _appStateService: AppStateService, private readonly _appStateService: AppStateService,
private readonly _userService: UserService,
private readonly _dialogService: DossiersDialogService, private readonly _dialogService: DossiersDialogService,
private readonly _fileManagementControllerService: FileManagementControllerService, private readonly _fileManagementControllerService: FileManagementControllerService,
private readonly _reanalysisControllerService: ReanalysisControllerService, private readonly _reanalysisControllerService: ReanalysisControllerService,
private readonly _permissionsService: PermissionsService, private readonly _permissionsService: PermissionsService,
private readonly _fileActionService: FileActionService, private readonly _fileActionService: FileActionService,
private readonly _statusOverlayService: StatusOverlayService,
private readonly _changeDetectorRef: ChangeDetectorRef,
private readonly _loadingService: LoadingService, private readonly _loadingService: LoadingService,
private readonly _screenStateService: ScreenStateService<FileStatusWrapper> private readonly _screenStateService: ScreenStateService<FileStatusWrapper>
) {} ) {}

View File

@ -72,7 +72,7 @@ export class DossierDetailsComponent implements OnInit {
key: key key: key
}); });
} }
this.documentsChartData.sort(StatusSorter.byKey); this.documentsChartData.sort(StatusSorter.byStatus);
this.documentsChartData = this.translateChartService.translateStatus(this.documentsChartData); this.documentsChartData = this.translateChartService.translateStatus(this.documentsChartData);
this._changeDetectorRef.detectChanges(); this._changeDetectorRef.detectChanges();
} }

View File

@ -2,7 +2,6 @@ import { Component, EventEmitter, Input, Output } from '@angular/core';
import { PermissionsService } from '@services/permissions.service'; import { PermissionsService } from '@services/permissions.service';
import { DossierWrapper } from '@state/model/dossier.wrapper'; import { DossierWrapper } from '@state/model/dossier.wrapper';
import { StatusSorter } from '@utils/sorters/status-sorter'; import { StatusSorter } from '@utils/sorters/status-sorter';
import { FileManagementControllerService } from '@redaction/red-ui-http';
import { AppStateService } from '@state/app-state.service'; import { AppStateService } from '@state/app-state.service';
import { DossiersDialogService } from '../../services/dossiers-dialog.service'; import { DossiersDialogService } from '../../services/dossiers-dialog.service';
@ -19,16 +18,13 @@ export class DossierListingActionsComponent {
constructor( constructor(
readonly permissionsService: PermissionsService, readonly permissionsService: PermissionsService,
readonly appStateService: AppStateService, readonly appStateService: AppStateService,
private readonly _dialogService: DossiersDialogService, private readonly _dialogService: DossiersDialogService
private readonly _fileManagementControllerService: FileManagementControllerService
) {} ) {}
openEditDossierDialog($event: MouseEvent, dossierWrapper: DossierWrapper) { openEditDossierDialog($event: MouseEvent, dossierWrapper: DossierWrapper) {
this._dialogService.openDialog('editDossier', $event, { this._dialogService.openDialog('editDossier', $event, {
dossierWrapper, dossierWrapper,
afterSave: () => { afterSave: () => this.actionPerformed.emit()
this.actionPerformed.emit();
}
}); });
} }
@ -51,7 +47,7 @@ export class DossierListingActionsComponent {
}, {}); }, {});
return Object.keys(obj) return Object.keys(obj)
.sort((a, b) => StatusSorter[a] - StatusSorter[b]) .sort(StatusSorter.byStatus)
.map(status => ({ length: obj[status], color: status })); .map(status => ({ length: obj[status], color: status }));
} }
} }

View File

@ -18,10 +18,8 @@
<div class="table-header" redactionSyncWidth="table-item"> <div class="table-header" redactionSyncWidth="table-item">
<redaction-table-col-name <redaction-table-col-name
(toggleSort)="toggleSort($event)"
[activeSortingOption]="sortingOption"
[withSort]="true" [withSort]="true"
column="dossier.dossierName" column="dossierName"
label="dossier-listing.table-col-names.name" label="dossier-listing.table-col-names.name"
></redaction-table-col-name> ></redaction-table-col-name>
@ -45,7 +43,7 @@
<cdk-virtual-scroll-viewport [itemSize]="itemSize" redactionHasScrollbar> <cdk-virtual-scroll-viewport [itemSize]="itemSize" redactionHasScrollbar>
<div <div
*cdkVirtualFor="let dw of sortedDisplayedEntities$ | async" *cdkVirtualFor="let dw of sortedDisplayedEntities$ | async; trackBy: trackByPrimaryKey"
[class.pointer]="!!dw" [class.pointer]="!!dw"
[routerLink]="[!!dw ? '/main/dossiers/' + dw.dossier.dossierId : []]" [routerLink]="[!!dw ? '/main/dossiers/' + dw.dossier.dossierId : []]"
class="table-item" class="table-item"

View File

@ -28,7 +28,7 @@ import { FilterService } from '@shared/services/filter.service';
import { SearchService } from '@shared/services/search.service'; import { SearchService } from '@shared/services/search.service';
import { ScreenStateService } from '@shared/services/screen-state.service'; import { ScreenStateService } from '@shared/services/screen-state.service';
import { BaseListingComponent } from '@shared/base/base-listing.component'; import { BaseListingComponent } from '@shared/base/base-listing.component';
import { ScreenNames, SortingService } from '@services/sorting.service'; import { SortingService } from '@services/sorting.service';
const isLeavingScreen = event => event instanceof NavigationStart && event.url !== '/main/dossiers'; const isLeavingScreen = event => event instanceof NavigationStart && event.url !== '/main/dossiers';
@ -39,6 +39,8 @@ const isLeavingScreen = event => event instanceof NavigationStart && event.url !
}) })
export class DossierListingScreenComponent extends BaseListingComponent<DossierWrapper> implements OnInit, OnDestroy, OnAttach, OnDetach { export class DossierListingScreenComponent extends BaseListingComponent<DossierWrapper> implements OnInit, OnDestroy, OnAttach, OnDetach {
readonly itemSize = 95; readonly itemSize = 95;
protected readonly _primaryKey = 'dossierName';
dossiersChartData: DoughnutChartConfig[] = []; dossiersChartData: DoughnutChartConfig[] = [];
documentsChartData: DoughnutChartConfig[] = []; documentsChartData: DoughnutChartConfig[] = [];
buttonConfigs: ButtonConfig[] = [ buttonConfigs: ButtonConfig[] = [
@ -68,8 +70,6 @@ export class DossierListingScreenComponent extends BaseListingComponent<DossierW
protected readonly _injector: Injector protected readonly _injector: Injector
) { ) {
super(_injector); super(_injector);
this.sortingService.setScreenName(ScreenNames.DOSSIER_LISTING);
this.searchService.setSearchKey('name');
this._appStateService.reset(); this._appStateService.reset();
this._loadEntitiesFromState(); this._loadEntitiesFromState();
} }
@ -153,7 +153,7 @@ export class DossierListingScreenComponent extends BaseListingComponent<DossierW
key: key key: key
}); });
} }
this.documentsChartData.sort(StatusSorter.byKey); this.documentsChartData.sort(StatusSorter.byStatus);
this.documentsChartData = this._translateChartService.translateStatus(this.documentsChartData); this.documentsChartData = this._translateChartService.translateStatus(this.documentsChartData);
} }
@ -192,7 +192,7 @@ export class DossierListingScreenComponent extends BaseListingComponent<DossierW
slug: 'statusFilters', slug: 'statusFilters',
label: this._translateService.instant('filters.status'), label: this._translateService.instant('filters.status'),
icon: 'red:status', icon: 'red:status',
values: statusFilters.sort(StatusSorter.byKey), values: statusFilters.sort(StatusSorter.byStatus),
checker: dossierStatusChecker checker: dossierStatusChecker
}); });

View File

@ -59,16 +59,12 @@
<div class="select-oval-placeholder"></div> <div class="select-oval-placeholder"></div>
<redaction-table-col-name <redaction-table-col-name
(toggleSort)="toggleSort($event)"
[activeSortingOption]="sortingOption"
[withSort]="true" [withSort]="true"
column="filename" column="filename"
label="dossier-overview.table-col-names.name" label="dossier-overview.table-col-names.name"
></redaction-table-col-name> ></redaction-table-col-name>
<redaction-table-col-name <redaction-table-col-name
(toggleSort)="toggleSort($event)"
[activeSortingOption]="sortingOption"
[withSort]="true" [withSort]="true"
column="added" column="added"
label="dossier-overview.table-col-names.added-on" label="dossier-overview.table-col-names.added-on"
@ -77,8 +73,6 @@
<redaction-table-col-name label="dossier-overview.table-col-names.needs-work"></redaction-table-col-name> <redaction-table-col-name label="dossier-overview.table-col-names.needs-work"></redaction-table-col-name>
<redaction-table-col-name <redaction-table-col-name
(toggleSort)="toggleSort($event)"
[activeSortingOption]="sortingOption"
[withSort]="true" [withSort]="true"
class="user-column" class="user-column"
column="reviewerName" column="reviewerName"
@ -86,16 +80,12 @@
></redaction-table-col-name> ></redaction-table-col-name>
<redaction-table-col-name <redaction-table-col-name
(toggleSort)="toggleSort($event)"
[activeSortingOption]="sortingOption"
[withSort]="true" [withSort]="true"
column="pages" column="pages"
label="dossier-overview.table-col-names.pages" label="dossier-overview.table-col-names.pages"
></redaction-table-col-name> ></redaction-table-col-name>
<redaction-table-col-name <redaction-table-col-name
(toggleSort)="toggleSort($event)"
[activeSortingOption]="sortingOption"
[withSort]="true" [withSort]="true"
class="flex-end" class="flex-end"
column="statusSort" column="statusSort"
@ -116,7 +106,7 @@
<cdk-virtual-scroll-viewport [itemSize]="itemSize" redactionHasScrollbar> <cdk-virtual-scroll-viewport [itemSize]="itemSize" redactionHasScrollbar>
<div <div
*cdkVirtualFor="let fileStatus of sortedDisplayedEntities$ | async; trackBy: trackByFileId" *cdkVirtualFor="let fileStatus of sortedDisplayedEntities$ | async; trackBy: trackByPrimaryKey"
[class.disabled]="fileStatus.isExcluded" [class.disabled]="fileStatus.isExcluded"
[class.last-opened]="isLastOpenedFile(fileStatus)" [class.last-opened]="isLastOpenedFile(fileStatus)"
[class.pointer]="permissionsService.canOpenFile(fileStatus)" [class.pointer]="permissionsService.canOpenFile(fileStatus)"

View File

@ -26,7 +26,7 @@ import { ActionConfig } from '@shared/components/page-header/models/action-confi
import { FilterService } from '@shared/services/filter.service'; import { FilterService } from '@shared/services/filter.service';
import { SearchService } from '@shared/services/search.service'; import { SearchService } from '@shared/services/search.service';
import { ScreenStateService } from '@shared/services/screen-state.service'; import { ScreenStateService } from '@shared/services/screen-state.service';
import { ScreenNames, SortingService } from '@services/sorting.service'; import { SortingService } from '../../../../services/sorting.service';
import { BaseListingComponent } from '@shared/base/base-listing.component'; import { BaseListingComponent } from '@shared/base/base-listing.component';
import { LoadingService } from '@services/loading.service'; import { LoadingService } from '@services/loading.service';
import { DossierAttributesService } from '@shared/services/controller-wrappers/dossier-attributes.service'; import { DossierAttributesService } from '@shared/services/controller-wrappers/dossier-attributes.service';
@ -42,8 +42,9 @@ export class DossierOverviewScreenComponent
extends BaseListingComponent<FileStatusWrapper> extends BaseListingComponent<FileStatusWrapper>
implements OnInit, OnDestroy, OnDetach, OnAttach implements OnInit, OnDestroy, OnDetach, OnAttach
{ {
collapsedDetails = false;
readonly itemSize = 80; readonly itemSize = 80;
protected readonly _primaryKey = 'filename';
collapsedDetails = false;
actionConfigs: ActionConfig[]; actionConfigs: ActionConfig[];
dossierAttributes: DossierAttributeWithValue[] = []; dossierAttributes: DossierAttributeWithValue[] = [];
@ViewChild(DossierDetailsComponent, { static: false }) @ViewChild(DossierDetailsComponent, { static: false })
@ -73,8 +74,6 @@ export class DossierOverviewScreenComponent
protected readonly _injector: Injector protected readonly _injector: Injector
) { ) {
super(_injector); super(_injector);
this.sortingService.setScreenName(ScreenNames.DOSSIER_OVERVIEW);
this.searchService.setSearchKey('searchField');
this._loadEntitiesFromState(); this._loadEntitiesFromState();
} }
@ -82,8 +81,8 @@ export class DossierOverviewScreenComponent
return this._appStateService.activeDossier; return this._appStateService.activeDossier;
} }
get user() { get userId() {
return this._userService.user; return this._userService.userId;
} }
get checkedRequiredFilters() { get checkedRequiredFilters() {
@ -170,10 +169,6 @@ export class DossierOverviewScreenComponent
this._changeDetectorRef.detectChanges(); this._changeDetectorRef.detectChanges();
} }
trackByFileId(index: number, item: FileStatusWrapper) {
return item.fileId;
}
@HostListener('drop', ['$event']) @HostListener('drop', ['$event'])
onDrop(event: DragEvent) { onDrop(event: DragEvent) {
handleFileDrop(event, this.activeDossier, this._uploadFiles.bind(this)); handleFileDrop(event, this.activeDossier, this._uploadFiles.bind(this));
@ -272,7 +267,7 @@ export class DossierOverviewScreenComponent
slug: 'statusFilters', slug: 'statusFilters',
label: this._translateService.instant('filters.status'), label: this._translateService.instant('filters.status'),
icon: 'red:status', icon: 'red:status',
values: statusFilters.sort(StatusSorter.byKey), values: statusFilters.sort(StatusSorter.byStatus),
checker: keyChecker('status') checker: keyChecker('status')
}); });
@ -348,7 +343,7 @@ export class DossierOverviewScreenComponent
{ {
key: 'assigned-to-me', key: 'assigned-to-me',
label: this._translateService.instant('dossier-overview.quick-filters.assigned-to-me'), label: this._translateService.instant('dossier-overview.quick-filters.assigned-to-me'),
checker: (file: FileStatusWrapper) => file.currentReviewer === this.user.id checker: (file: FileStatusWrapper) => file.currentReviewer === this.userId
}, },
{ {
key: 'unassigned', key: 'unassigned',
@ -358,7 +353,7 @@ export class DossierOverviewScreenComponent
{ {
key: 'assigned-to-others', key: 'assigned-to-others',
label: this._translateService.instant('dossier-overview.quick-filters.assigned-to-others'), label: this._translateService.instant('dossier-overview.quick-filters.assigned-to-others'),
checker: (file: FileStatusWrapper) => !!file.currentReviewer && file.currentReviewer !== this.user.id checker: (file: FileStatusWrapper) => !!file.currentReviewer && file.currentReviewer !== this.userId
} }
]; ];
} }

View File

@ -1,5 +1,5 @@
import { Component, Injector, OnDestroy, ViewChild } from '@angular/core'; import { Component, Injector, OnDestroy, ViewChild } from '@angular/core';
import { SortingOption, SortingService } from '@services/sorting.service'; import { SortingOrders, SortingService } from '@services/sorting.service';
import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling'; import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
import { FilterService } from '../services/filter.service'; import { FilterService } from '../services/filter.service';
import { SearchService } from '../services/search.service'; import { SearchService } from '../services/search.service';
@ -20,13 +20,30 @@ export abstract class BaseListingComponent<T> extends AutoUnsubscribeComponent i
readonly searchService: SearchService<T>; readonly searchService: SearchService<T>;
readonly screenStateService: ScreenStateService<T>; readonly screenStateService: ScreenStateService<T>;
/**
* Key used in the *trackBy* function with **ngFor* or **cdkVirtualFor*
* and in the default sorting and as the search field
* @protected
*/
protected abstract _primaryKey: string;
protected constructor(protected readonly _injector: Injector) { protected constructor(protected readonly _injector: Injector) {
super(); super();
this.trackByPrimaryKey = this.trackByPrimaryKey.bind(this);
this.permissionsService = this._injector.get(PermissionsService); this.permissionsService = this._injector.get(PermissionsService);
this.filterService = this._injector.get(FilterService); this.filterService = this._injector.get(FilterService);
this.sortingService = this._injector.get(SortingService); this.sortingService = this._injector.get(SortingService);
this.searchService = this._injector.get(SearchService); this.searchService = this._injector.get(SearchService);
this.screenStateService = this._injector.get<ScreenStateService<T>>(ScreenStateService); this.screenStateService = this._injector.get<ScreenStateService<T>>(ScreenStateService);
setTimeout(() => this.setInitialConfig());
}
setInitialConfig() {
this.sortingService.setSortingOption({
column: this._primaryKey,
order: SortingOrders.ASC
});
this.searchService.setSearchKey(this._primaryKey);
} }
ngOnDestroy(): void { ngOnDestroy(): void {
@ -45,22 +62,16 @@ export abstract class BaseListingComponent<T> extends AutoUnsubscribeComponent i
return this.screenStateService.allEntities; return this.screenStateService.allEntities;
} }
get sortingOption(): SortingOption {
return this.sortingService.getSortingOption();
}
get noMatch$(): Observable<boolean> { get noMatch$(): Observable<boolean> {
return combineLatest([this.screenStateService.allEntitiesLength$, this.screenStateService.displayedLength$]).pipe( return combineLatest([this.screenStateService.allEntitiesLength$, this.screenStateService.displayedLength$]).pipe(
map(res => res[0] && !res[1]) map(([hasEntities, hasDisplayedEntities]) => hasEntities && !hasDisplayedEntities)
); );
} }
canBulkDelete$(hasPermission = true) { canBulkDelete$(hasPermission = true) {
return this.screenStateService.areSomeEntitiesSelected$.pipe(map(res => res && hasPermission)); return this.screenStateService.areSomeEntitiesSelected$.pipe(
} map(areSomeEntitiesSelected => areSomeEntitiesSelected && hasPermission)
);
toggleSort($event) {
this.sortingService.toggleSort($event);
} }
toggleSelectAll() { toggleSelectAll() {
@ -75,4 +86,8 @@ export abstract class BaseListingComponent<T> extends AutoUnsubscribeComponent i
isSelected(entity: T): boolean { isSelected(entity: T): boolean {
return this.screenStateService.isSelected(entity); return this.screenStateService.isSelected(entity);
} }
trackByPrimaryKey(index: number, item: T) {
return item[this._primaryKey];
}
} }

View File

@ -16,20 +16,16 @@
</ng-container> </ng-container>
<redaction-input-with-action <redaction-input-with-action
*ngIf="searchService.isSearchNeeded" *ngIf="searchPlaceholder"
[form]="searchService.searchForm" [form]="searchService.searchForm"
[placeholder]="searchPlaceholder" [placeholder]="searchPlaceholder"
type="search" type="search"
></redaction-input-with-action> ></redaction-input-with-action>
<div <div (click)="resetFilters()" *ngIf="showResetFilters$ | async" class="reset-filters" translate="reset-filters"></div>
(click)="resetFilters()"
*ngIf="(filterService.showResetFilters$ | async) || searchService.searchValue"
class="reset-filters"
translate="reset-filters"
></div>
</div> </div>
<div class="actions" *ngIf="showCloseButton || actionConfigs || buttonConfigs">
<ng-container *ngFor="let config of buttonConfigs; trackBy: trackByLabel"> <ng-container *ngFor="let config of buttonConfigs; trackBy: trackByLabel">
<redaction-icon-button <redaction-icon-button
(action)="config.action($event)" (action)="config.action($event)"
@ -40,7 +36,6 @@
></redaction-icon-button> ></redaction-icon-button>
</ng-container> </ng-container>
<div class="actions" *ngIf="showCloseButton || actionConfigs">
<ng-container *ngFor="let config of actionConfigs; trackBy: trackByLabel"> <ng-container *ngFor="let config of actionConfigs; trackBy: trackByLabel">
<redaction-circle-button <redaction-circle-button
(action)="config.action($event)" (action)="config.action($event)"
@ -56,7 +51,7 @@
<redaction-circle-button <redaction-circle-button
[class.ml-6]="actionConfigs" [class.ml-6]="actionConfigs"
*ngIf="showCloseButton && permissionsService.isUser()" *ngIf="showCloseButton"
icon="red:close" icon="red:close"
redactionNavigateLastDossiersScreen redactionNavigateLastDossiersScreen
tooltip="common.close" tooltip="common.close"

View File

@ -1,10 +1,11 @@
import { Component, Input } from '@angular/core'; import { Component, Input, Optional } from '@angular/core';
import { PermissionsService } from '@services/permissions.service';
import { ActionConfig } from '@shared/components/page-header/models/action-config.model'; import { ActionConfig } from '@shared/components/page-header/models/action-config.model';
import { ButtonConfig } from '@shared/components/page-header/models/button-config.model'; import { ButtonConfig } from '@shared/components/page-header/models/button-config.model';
import { FilterService } from '@shared/services/filter.service'; import { FilterService } from '@shared/services/filter.service';
import { SearchService } from '@shared/services/search.service'; import { SearchService } from '@shared/services/search.service';
import { map } from 'rxjs/operators'; import { distinctUntilChanged, map } from 'rxjs/operators';
import { combineLatest, Observable } from 'rxjs';
import { FilterWrapper } from '@shared/components/filters/popup-filter/model/filter-wrapper.model';
@Component({ @Component({
selector: 'redaction-page-header', selector: 'redaction-page-header',
@ -18,22 +19,29 @@ export class PageHeaderComponent<T> {
@Input() buttonConfigs: ButtonConfig[]; @Input() buttonConfigs: ButtonConfig[];
@Input() searchPlaceholder: string; @Input() searchPlaceholder: string;
constructor( constructor(@Optional() readonly filterService: FilterService<T>, @Optional() readonly searchService: SearchService<T>) {}
readonly permissionsService: PermissionsService,
readonly filterService: FilterService<T>,
readonly searchService: SearchService<T>
) {}
get filters$() { get filters$(): Observable<FilterWrapper[]> {
return this.filterService.allFilters$.pipe(map(all => all.filter(f => f.icon))); return this.filterService?.allFilters$.pipe(map(all => all.filter(f => f.icon)));
} }
resetFilters() { get showResetFilters$(): Observable<boolean> {
const filtersLength$ = this.filters$.pipe(
map(f => f.length),
distinctUntilChanged()
);
return combineLatest([filtersLength$, this.filterService.showResetFilters$, this.searchService.valueChanges$]).pipe(
map(([hasFilters, showResetFilters, searchValue]) => hasFilters && (showResetFilters || !!searchValue)),
distinctUntilChanged()
);
}
resetFilters(): void {
this.filterService.reset(); this.filterService.reset();
this.searchService.reset(); this.searchService.reset();
} }
trackByLabel(index: number, item) { trackByLabel<K extends { label?: string }>(index: number, item: K): string {
return item.label; return item.label;
} }
} }

View File

@ -1,18 +1,13 @@
<div (click)="withSort && toggleSort.emit(column)" [class.pointer]="withSort" [ngClass]="class"> <div (click)="withSort && sortingService?.toggleSort(column)" [class.pointer]="withSort" [ngClass]="class">
<mat-icon *ngIf="!!leftIcon" [svgIcon]="leftIcon"></mat-icon> <mat-icon *ngIf="!!leftIcon" [svgIcon]="leftIcon"></mat-icon>
<span [translate]="label" class="all-caps-label"></span> <span [translate]="label" class="all-caps-label"></span>
<mat-icon <mat-icon *ngIf="!!rightIcon" [matTooltip]="rightIconTooltip | translate" [svgIcon]="rightIcon" matTooltipPosition="above"></mat-icon>
*ngIf="!!rightIcon"
[matTooltip]="rightIconTooltip | translate"
[svgIcon]="rightIcon"
matTooltipPosition="above"
></mat-icon>
<div <div
*ngIf="withSort" *ngIf="withSort && sortingService"
[class.force-display]="activeSortingOption.column === column" [class.force-display]="sortingService.sortingOption?.column === column"
class="sort-arrows-container" class="sort-arrows-container"
> >
<mat-icon *ngIf="activeSortingOption?.order === 'asc'" svgIcon="red:sort-asc"></mat-icon> <mat-icon *ngIf="sortingService.sortingOption?.order === 'asc'" svgIcon="red:sort-asc"></mat-icon>
<mat-icon *ngIf="activeSortingOption?.order === 'desc'" svgIcon="red:sort-desc"></mat-icon> <mat-icon *ngIf="sortingService.sortingOption?.order === 'desc'" svgIcon="red:sort-desc"></mat-icon>
</div> </div>
</div> </div>

View File

@ -1,5 +1,5 @@
import { Component, EventEmitter, Input, Output } from '@angular/core'; import { Component, Input, Optional } from '@angular/core';
import { SortingOption } from '@services/sorting.service'; import { SortingService } from '@services/sorting.service';
@Component({ @Component({
selector: 'redaction-table-col-name', selector: 'redaction-table-col-name',
@ -7,7 +7,6 @@ import { SortingOption } from '@services/sorting.service';
styleUrls: ['./table-col-name.component.scss'] styleUrls: ['./table-col-name.component.scss']
}) })
export class TableColNameComponent { export class TableColNameComponent {
@Input() activeSortingOption: SortingOption;
@Input() column: string; @Input() column: string;
@Input() label: string; @Input() label: string;
@Input() withSort = false; @Input() withSort = false;
@ -16,5 +15,5 @@ export class TableColNameComponent {
@Input() rightIcon: string; @Input() rightIcon: string;
@Input() rightIconTooltip: string; @Input() rightIconTooltip: string;
@Output() toggleSort = new EventEmitter<string>(); constructor(@Optional() readonly sortingService: SortingService) {}
} }

View File

@ -24,7 +24,7 @@ export class FilterService<T> {
get showResetFilters$() { get showResetFilters$() {
return this.allFilters$.pipe( return this.allFilters$.pipe(
map(all => this._toFlatFilters(all)), map(all => this._toFlatFilters(all)),
filter(f => !!f.find(el => el.checked)), map(f => !!f.find(el => el.checked)),
distinctUntilChanged() distinctUntilChanged()
); );
} }

View File

@ -74,7 +74,9 @@ export class ScreenStateService<T> {
} }
get areAllEntitiesSelected$(): Observable<boolean> { get areAllEntitiesSelected$(): Observable<boolean> {
return combineLatest([this.displayedLength$, this.selectedLength$]).pipe(map(res => res[0] && res[0] === res[1])); return combineLatest([this.displayedLength$, this.selectedLength$]).pipe(
map(([displayedLength, selectedLength]) => displayedLength && displayedLength === selectedLength)
);
} }
/** /**
@ -88,7 +90,9 @@ export class ScreenStateService<T> {
* Indicates that some entities are selected, but not all * Indicates that some entities are selected, but not all
*/ */
get notAllEntitiesSelected$(): Observable<boolean> { get notAllEntitiesSelected$(): Observable<boolean> {
return combineLatest([this.areAllEntitiesSelected$, this.areSomeEntitiesSelected$]).pipe(map(res => !res[0] && res[1])); return combineLatest([this.areAllEntitiesSelected$, this.areSomeEntitiesSelected$]).pipe(
map(([allEntitiesAreSelected, someEntitiesAreSelected]) => !allEntitiesAreSelected && someEntitiesAreSelected)
);
} }
isSelected(entity: T): boolean { isSelected(entity: T): boolean {

View File

@ -2,18 +2,24 @@ import { Injectable } from '@angular/core';
import { debounce } from '@utils/debounce'; import { debounce } from '@utils/debounce';
import { ScreenStateService } from '@shared/services/screen-state.service'; import { ScreenStateService } from '@shared/services/screen-state.service';
import { FormBuilder } from '@angular/forms'; import { FormBuilder } from '@angular/forms';
import { BehaviorSubject } from 'rxjs';
@Injectable() @Injectable()
export class SearchService<T> { export class SearchService<T> {
private _searchValue = ''; private _searchValue = '';
private _searchKey: string; private _searchKey: string;
valueChanges$ = new BehaviorSubject(this._searchValue);
readonly searchForm = this._formBuilder.group({ readonly searchForm = this._formBuilder.group({
query: [''] query: ['']
}); });
constructor(private readonly _screenStateService: ScreenStateService<T>, private readonly _formBuilder: FormBuilder) { constructor(private readonly _screenStateService: ScreenStateService<T>, private readonly _formBuilder: FormBuilder) {
this.searchForm.valueChanges.subscribe(() => this.executeSearch()); this.searchForm.valueChanges.subscribe(() => {
this.valueChanges$.next(this.searchValue.toLowerCase());
this.executeSearch();
});
} }
@debounce(200) @debounce(200)
@ -23,7 +29,9 @@ export class SearchService<T> {
} }
executeSearchImmediately(): void { executeSearchImmediately(): void {
const displayed = this._screenStateService.filteredEntities || this._screenStateService.allEntities; const displayed = this._screenStateService.filteredEntities.length
? this._screenStateService.filteredEntities
: this._screenStateService.allEntities;
if (!this._searchKey) { if (!this._searchKey) {
return this._screenStateService.setDisplayedEntities(displayed); return this._screenStateService.setDisplayedEntities(displayed);
@ -39,10 +47,6 @@ export class SearchService<T> {
this._searchKey = value; this._searchKey = value;
} }
get isSearchNeeded(): boolean {
return !!this._searchKey;
}
get searchValue(): string { get searchValue(): string {
return this.searchForm.get('query').value; return this.searchForm.get('query').value;
} }

View File

@ -13,40 +13,12 @@ export interface SortingOption {
column: string; column: string;
} }
export type ScreenName =
| 'dossier-listing'
| 'dossier-overview'
| 'dictionary-listing'
| 'dossier-templates-listing'
| 'default-colors'
| 'file-attributes-listing'
| 'dossier-attributes-listing';
export const enum ScreenNames {
DOSSIER_LISTING = 'dossier-listing',
DOSSIER_OVERVIEW = 'dossier-overview',
DICTIONARY_LISTING = 'dictionary-listing',
DOSSIER_TEMPLATES_LISTING = 'dossier-templates-listing',
DEFAULT_COLORS = 'default-colors',
FILE_ATTRIBUTES_LISTING = 'file-attributes-listing',
DOSSIER_ATTRIBUTES_LISTING = 'dossier-attributes-listing'
}
@Injectable() @Injectable()
export class SortingService { export class SortingService {
private _currentScreenName: string; private _sortingOption: SortingOption;
private readonly _options: { [key in ScreenName]: SortingOption } = {
[ScreenNames.DOSSIER_LISTING]: { column: 'dossier.dossierName', order: SortingOrders.ASC },
[ScreenNames.DOSSIER_OVERVIEW]: { column: 'filename', order: SortingOrders.ASC },
[ScreenNames.DICTIONARY_LISTING]: { column: 'label', order: SortingOrders.ASC },
[ScreenNames.DOSSIER_TEMPLATES_LISTING]: { column: 'name', order: SortingOrders.ASC },
[ScreenNames.DEFAULT_COLORS]: { column: 'key', order: SortingOrders.ASC },
[ScreenNames.FILE_ATTRIBUTES_LISTING]: { column: 'label', order: SortingOrders.ASC },
[ScreenNames.DOSSIER_ATTRIBUTES_LISTING]: { column: 'label', order: 'asc' }
};
setScreenName(value: string) { setSortingOption(value: SortingOption): void {
this._currentScreenName = value; this._sortingOption = value;
} }
sort<T>(values: T[], order = '', column: string = ''): T[] { sort<T>(values: T[], order = '', column: string = ''): T[] {
@ -67,26 +39,26 @@ export class SortingService {
} }
defaultSort<T>(values: T[]) { defaultSort<T>(values: T[]) {
return this.sort(values, this.getSortingOption().order, this.getSortingOption().column); return this.sort(values, this.sortingOption?.order, this.sortingOption?.column);
} }
toggleSort(column: string) { toggleSort(column: string) {
if (this._options[this._currentScreenName].column === column) { if (this._sortingOption.column === column) {
this._currentOrder = this._currentOrder === SortingOrders.ASC ? SortingOrders.DESC : SortingOrders.ASC; this._currentOrder = this._currentOrder === SortingOrders.ASC ? SortingOrders.DESC : SortingOrders.ASC;
} else { } else {
this._options[this._currentScreenName] = { column, order: SortingOrders.ASC }; this._sortingOption = { column, order: SortingOrders.ASC };
} }
} }
getSortingOption(): SortingOption { get sortingOption(): SortingOption {
return this._options[this._currentScreenName]; return this._sortingOption;
} }
private get _currentOrder(): string { private get _currentOrder(): SortingOrder {
return this._options[this._currentScreenName].order; return this._sortingOption.order;
} }
private set _currentOrder(value: string) { private set _currentOrder(value: SortingOrder) {
this._options[this._currentScreenName].order = value; this._sortingOption.order = value;
} }
} }

View File

@ -1,3 +1,13 @@
type StatusSorterItem = { key?: string } | string;
const byStatus = (a: StatusSorterItem, b: StatusSorterItem) => {
if (typeof a !== typeof b) return;
const x = typeof a === 'string' ? a : a.key;
const y = typeof b === 'string' ? b : b.key;
return (StatusSorter[x] = StatusSorter[y]);
};
export const StatusSorter = { export const StatusSorter = {
ERROR: 0, ERROR: 0,
UNPROCESSED: 1, UNPROCESSED: 1,
@ -9,5 +19,5 @@ export const StatusSorter = {
UNDER_REVIEW: 15, UNDER_REVIEW: 15,
UNDER_APPROVAL: 20, UNDER_APPROVAL: 20,
APPROVED: 25, APPROVED: 25,
byKey: (a: { key?: string }, b: { key?: string }) => StatusSorter[a.key] - StatusSorter[b.key] byStatus: byStatus
}; };