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
(mouseenter)="setHoveredColumn.emit(field.csvColumn)"
(mouseleave)="setHoveredColumn.emit()"
*cdkVirtualFor="let field of displayedEntities$ | async"
*cdkVirtualFor="let field of displayedEntities$ | async; trackBy: trackByPrimaryKey"
class="table-item"
>
<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>();
readonly typeOptions = [FileAttributeConfig.TypeEnum.TEXT, FileAttributeConfig.TypeEnum.NUMBER, FileAttributeConfig.TypeEnum.DATE];
protected readonly _primaryKey = 'id';
constructor(protected readonly _injector: Injector) {
super(_injector);

View File

@ -101,7 +101,7 @@
(click)="toggleFieldActive(field)"
(mouseenter)="setHoveredColumn(field.csvColumn)"
(mouseleave)="setHoveredColumn()"
*ngFor="let field of displayedEntities$ | async"
*ngFor="let field of displayedEntities$ | async; trackBy: trackByPrimaryKey"
class="csv-header-pill-wrapper"
>
<div [class.selected]="isActive(field)" class="csv-header-pill">
@ -109,7 +109,10 @@
{{ field.csvColumn }}
</div>
<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>
</div>

View File

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

View File

@ -22,10 +22,7 @@
<div class="content-container">
<div class="header-item">
<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>
<div class="actions-wrapper">
<redaction-pagination
@ -37,10 +34,7 @@
<div class="red-input-group w-150 mr-20">
<mat-form-field class="no-label">
<mat-select formControlName="category">
<mat-option
*ngFor="let category of categories"
[value]="category"
>
<mat-option *ngFor="let category of categories" [value]="category">
{{ 'audit-screen.categories.' + category | translate }}
</mat-option>
</mat-select>
@ -56,10 +50,7 @@
[withName]="true"
size="small"
></redaction-initials-avatar>
<div
*ngIf="filterForm.get('userId').value === ALL_USERS"
[translate]="ALL_USERS"
></div>
<div *ngIf="filterForm.get('userId').value === ALL_USERS" [translate]="ALL_USERS"></div>
</mat-select-trigger>
<mat-option *ngFor="let userId of userIds" [value]="userId">
<redaction-initials-avatar
@ -68,26 +59,16 @@
[withName]="true"
size="small"
></redaction-initials-avatar>
<div
*ngIf="userId === ALL_USERS"
[translate]="ALL_USERS"
></div>
<div *ngIf="userId === ALL_USERS" [translate]="ALL_USERS"></div>
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div class="separator">·</div>
<div class="red-input-group datepicker-wrapper mr-20">
<input
[matDatepicker]="fromPicker"
formControlName="from"
placeholder="dd/mm/yy"
/>
<input [matDatepicker]="fromPicker" formControlName="from" placeholder="dd/mm/yy" />
<mat-datepicker-toggle [for]="fromPicker" matSuffix>
<mat-icon
matDatepickerToggleIcon
svgIcon="red:calendar"
></mat-icon>
<mat-icon matDatepickerToggleIcon svgIcon="red:calendar"></mat-icon>
</mat-datepicker-toggle>
<mat-datepicker #fromPicker></mat-datepicker>
</div>
@ -95,16 +76,9 @@
<div class="mr-20" translate="audit-screen.to"></div>
<div class="red-input-group datepicker-wrapper">
<input
[matDatepicker]="toPicker"
formControlName="to"
placeholder="dd/mm/yy"
/>
<input [matDatepicker]="toPicker" formControlName="to" placeholder="dd/mm/yy" />
<mat-datepicker-toggle [for]="toPicker" matSuffix>
<mat-icon
matDatepickerToggleIcon
svgIcon="red:calendar"
></mat-icon>
<mat-icon matDatepickerToggleIcon svgIcon="red:calendar"></mat-icon>
</mat-datepicker-toggle>
<mat-datepicker #toPicker></mat-datepicker>
</div>
@ -113,31 +87,18 @@
</div>
<div class="table-header" redactionSyncWidth="table-item">
<redaction-table-col-name
column="message"
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 column="message" 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
class="user-column"
column="user"
label="audit-screen.table-col-names.user"
></redaction-table-col-name>
<redaction-table-col-name
column="category"
label="audit-screen.table-col-names.category"
></redaction-table-col-name>
<redaction-table-col-name column="category" label="audit-screen.table-col-names.category"></redaction-table-col-name>
<div class="scrollbar-placeholder"></div>
</div>
<redaction-empty-state
*ngIf="!logs?.totalHits"
icon="red:document"
screen="audit-screen"
></redaction-empty-state>
<redaction-empty-state *ngIf="!logs?.totalHits" icon="red:document" screen="audit-screen"></redaction-empty-state>
<cdk-virtual-scroll-viewport [itemSize]="80" redactionHasScrollbar>
<div *cdkVirtualFor="let log of logs?.data" class="table-item">
@ -148,11 +109,7 @@
{{ log.recordDate | date: 'd MMM. yyyy, hh:mm a' }}
</div>
<div class="user-column">
<redaction-initials-avatar
[userId]="log.userId"
[withName]="true"
size="small"
></redaction-initials-avatar>
<redaction-initials-avatar [userId]="log.userId" [withName]="true" size="small"></redaction-initials-avatar>
</div>
<div [translate]="'audit-screen.categories.' + log.category"></div>
<div class="scrollbar-placeholder"></div>
@ -162,7 +119,3 @@
</div>
</div>
</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 { FormBuilder, FormGroup } from '@angular/forms';
import { AuditControllerService, AuditResponse, AuditSearchRequest } from '@redaction/red-ui-http';
import { TranslateService } from '@ngx-translate/core';
import { Moment } from 'moment';
import { applyIntervalConstraints } from '@utils/date-inputs-utils';
import { LoadingService } from '../../../../services/loading.service';
import { AutoUnsubscribeComponent } from '../../../shared/base/auto-unsubscribe.component';
const PAGE_SIZE = 50;
@ -13,16 +14,14 @@ const PAGE_SIZE = 50;
templateUrl: './audit-screen.component.html',
styleUrls: ['./audit-screen.component.scss']
})
export class AuditScreenComponent {
export class AuditScreenComponent extends AutoUnsubscribeComponent implements OnDestroy {
readonly ALL_CATEGORIES = 'all-categories';
readonly ALL_USERS = 'audit-screen.all-users';
filterForm: FormGroup;
viewReady = false;
categories: string[] = [];
userIds: Set<string>;
logs: AuditResponse;
currentPage = 1;
private _previousFrom: Moment;
private _previousTo: Moment;
@ -31,8 +30,9 @@ export class AuditScreenComponent {
readonly permissionsService: PermissionsService,
private readonly _formBuilder: FormBuilder,
private readonly _auditControllerService: AuditControllerService,
private readonly _translateService: TranslateService
private readonly _loadingService: LoadingService
) {
super();
this.filterForm = this._formBuilder.group({
category: [this.ALL_CATEGORIES],
userId: [this.ALL_USERS],
@ -40,7 +40,7 @@ export class AuditScreenComponent {
to: []
});
this.filterForm.valueChanges.subscribe(value => {
this.addSubscription = this.filterForm.valueChanges.subscribe(value => {
if (!this._updateDateFilters(value)) {
this._fetchData();
}
@ -61,16 +61,7 @@ export class AuditScreenComponent {
}
private _updateDateFilters(value): boolean {
if (
applyIntervalConstraints(
value,
this._previousFrom,
this._previousTo,
this.filterForm,
'from',
'to'
)
) {
if (applyIntervalConstraints(value, this._previousFrom, this._previousTo, this.filterForm, 'from', 'to')) {
return true;
}
@ -80,7 +71,7 @@ export class AuditScreenComponent {
}
private _fetchData(page?: number) {
this.viewReady = false;
this._loadingService.start();
const promises = [];
const category = this.filterForm.get('category').value;
const userId = this.filterForm.get('userId').value;
@ -110,7 +101,7 @@ export class AuditScreenComponent {
for (const id of this.logs.data.map(log => log.userId).filter(uid => !!uid)) {
this.userIds.add(id);
}
this.viewReady = true;
this._loadingService.stop();
});
}
}

View File

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

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

View File

@ -35,7 +35,7 @@
<redaction-circle-button
(action)="openDeleteDictionariesDialog($event)"
*ngIf="canBulkDelete$(permissionsService.isAdmin())"
*ngIf="canBulkDelete$(permissionsService.isAdmin()) | async"
icon="red:trash"
tooltip="dictionary-listing.bulk.delete"
type="dark-bg"
@ -63,16 +63,12 @@
<div class="select-oval-placeholder"></div>
<redaction-table-col-name
(toggleSort)="toggleSort($event)"
[activeSortingOption]="sortingOption"
[withSort]="true"
column="label"
label="dictionary-listing.table-col-names.type"
></redaction-table-col-name>
<redaction-table-col-name
(toggleSort)="toggleSort($event)"
[activeSortingOption]="sortingOption"
[withSort]="true"
class="flex-center"
column="rank"
@ -98,7 +94,11 @@
<redaction-empty-state *ngIf="noMatch$ | async" screen="dictionary-listing" type="no-match"></redaction-empty-state>
<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">
<redaction-round-checkbox [active]="isSelected(dict)"></redaction-round-checkbox>
</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 { DictionaryControllerService } from '@redaction/red-ui-http';
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 { SearchService } from '@shared/services/search.service';
import { ScreenStateService } from '@shared/services/screen-state.service';
import { ScreenNames, SortingService } from '@services/sorting.service';
import { SortingService } from '../../../../services/sorting.service';
import { BaseListingComponent } from '@shared/base/base-listing.component';
import { AdminDialogService } from '../../services/admin-dialog.service';
@ -31,6 +31,8 @@ const toChartConfig = (dict: TypeValueWrapper): DoughnutChartConfig => ({
export class DictionaryListingScreenComponent extends BaseListingComponent<TypeValueWrapper> implements OnInit {
chartData: DoughnutChartConfig[] = [];
protected readonly _primaryKey = 'label';
constructor(
private readonly _dialogService: AdminDialogService,
private readonly _dictionaryControllerService: DictionaryControllerService,
@ -43,8 +45,6 @@ export class DictionaryListingScreenComponent extends BaseListingComponent<TypeV
) {
super(_injector);
_loadingService.start();
this.sortingService.setScreenName(ScreenNames.DOSSIER_LISTING);
this.searchService.setSearchKey('label');
_appStateService.activateDossierTemplate(_activatedRoute.snapshot.params.dossierTemplateId);
}

View File

@ -4,20 +4,10 @@
<redaction-admin-side-nav type="settings"></redaction-admin-side-nav>
<div>
<div class="page-header">
<div class="breadcrumb" translate="digital-signature"></div>
<div class="actions">
<redaction-circle-button
*ngIf="permissionsService.isUser()"
class="ml-6"
icon="red:close"
redactionNavigateLastDossiersScreen
tooltip="common.close"
tooltipPosition="below"
></redaction-circle-button>
</div>
</div>
<redaction-page-header
[pageLabel]="'digital-signature' | translate"
[showCloseButton]="permissionsService.isUser()"
></redaction-page-header>
<div class="red-content-inner">
<div class="content-container">
@ -111,5 +101,3 @@
</div>
</div>
</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 { lastIndexOfEnd } from '@utils/functions';
import { AutoUnsubscribeComponent } from '../../../shared/base/auto-unsubscribe.component';
import { LoadingService } from '../../../../services/loading.service';
@Component({
selector: 'redaction-digital-signature-screen',
@ -15,13 +16,13 @@ export class DigitalSignatureScreenComponent extends AutoUnsubscribeComponent im
digitalSignature: DigitalSignature;
digitalSignatureForm: FormGroup;
viewReady = false;
digitalSignatureExists = false;
constructor(
private readonly _digitalSignatureControllerService: DigitalSignatureControllerService,
private readonly _toaster: Toaster,
private readonly _formBuilder: FormBuilder,
private readonly _loadingService: LoadingService,
readonly permissionsService: PermissionsService
) {
super();
@ -82,7 +83,7 @@ export class DigitalSignatureScreenComponent extends AutoUnsubscribeComponent im
}
loadDigitalSignatureAndInitializeForm() {
this.viewReady = false;
this._loadingService.start();
this.addSubscription = this._digitalSignatureControllerService
.getDigitalSignature()
.subscribe(
@ -97,7 +98,7 @@ export class DigitalSignatureScreenComponent extends AutoUnsubscribeComponent im
)
.add(() => {
this._initForm();
this.viewReady = true;
this._loadingService.stop();
});
}

View File

@ -65,16 +65,12 @@
<div class="select-oval-placeholder"></div>
<redaction-table-col-name
(toggleSort)="toggleSort($event)"
[activeSortingOption]="sortingOption"
[withSort]="true"
column="label"
label="dossier-attributes-listing.table-col-names.label"
></redaction-table-col-name>
<redaction-table-col-name label="dossier-attributes-listing.table-col-names.placeholder"></redaction-table-col-name>
<redaction-table-col-name
(toggleSort)="toggleSort($event)"
[activeSortingOption]="sortingOption"
[withSort]="true"
column="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>
<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">
<redaction-round-checkbox [active]="isSelected(attribute)"></redaction-round-checkbox>
</div>

View File

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

View File

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

View File

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

View File

@ -37,7 +37,7 @@
<redaction-circle-button
(click)="openConfirmDeleteAttributeDialog($event)"
*ngIf="permissionsService.isAdmin() && screenStateService.areSomeEntitiesSelected$ | async"
*ngIf="canBulkDelete$(permissionsService.isAdmin()) | async"
icon="red:trash"
tooltip="file-attributes-listing.bulk-actions.delete"
type="dark-bg"
@ -74,24 +74,18 @@
<div class="select-oval-placeholder"></div>
<redaction-table-col-name
(toggleSort)="toggleSort($event)"
[activeSortingOption]="sortingOption"
[withSort]="true"
column="label"
label="file-attributes-listing.table-col-names.name"
></redaction-table-col-name>
<redaction-table-col-name
(toggleSort)="toggleSort($event)"
[activeSortingOption]="sortingOption"
[withSort]="true"
column="type"
label="file-attributes-listing.table-col-names.type"
></redaction-table-col-name>
<redaction-table-col-name
(toggleSort)="toggleSort($event)"
[activeSortingOption]="sortingOption"
[withSort]="true"
class="flex-center"
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 { FileAttributeConfig, FileAttributesConfig, FileAttributesControllerService } from '@redaction/red-ui-http';
import { AppStateService } from '@state/app-state.service';
@ -8,7 +8,7 @@ import { LoadingService } from '@services/loading.service';
import { FilterService } from '@shared/services/filter.service';
import { SearchService } from '@shared/services/search.service';
import { ScreenStateService } from '@shared/services/screen-state.service';
import { ScreenNames, SortingService } from '@services/sorting.service';
import { SortingService } from '../../../../services/sorting.service';
import { BaseListingComponent } from '@shared/base/base-listing.component';
@Component({
@ -17,7 +17,9 @@ import { BaseListingComponent } from '@shared/base/base-listing.component';
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [FilterService, SearchService, ScreenStateService, SortingService]
})
export class FileAttributesListingScreenComponent extends BaseListingComponent<FileAttributeConfig> implements OnInit {
export class FileAttributesListingScreenComponent extends BaseListingComponent<FileAttributeConfig> implements OnInit, OnDestroy {
protected readonly _primaryKey = 'label';
private _existingConfiguration: FileAttributesConfig;
@ViewChild('fileInput') private _fileInput: ElementRef;
@ -31,8 +33,6 @@ export class FileAttributesListingScreenComponent extends BaseListingComponent<F
protected readonly _injector: Injector
) {
super(_injector);
this.sortingService.setScreenName(ScreenNames.FILE_ATTRIBUTES_LISTING);
this.searchService.setSearchKey('label');
_appStateService.activateDossierTemplate(_activatedRoute.snapshot.params.dossierTemplateId);
}

View File

@ -3,27 +3,12 @@
<redaction-admin-side-nav type="settings"></redaction-admin-side-nav>
<div *ngIf="viewReady">
<div class="page-header">
<div class="breadcrumb" translate="license-information"></div>
<div class="actions">
<button
(click)="sendMail()"
color="primary"
mat-flat-button
translate="license-info-screen.email-report"
></button>
<redaction-circle-button
*ngIf="permissionsService.isUser()"
class="ml-6"
icon="red:close"
redactionNavigateLastDossiersScreen
tooltip="common.close"
tooltipPosition="below"
></redaction-circle-button>
</div>
</div>
<div>
<redaction-page-header
[pageLabel]="'license-information' | translate"
[showCloseButton]="permissionsService.isUser()"
[buttonConfigs]="buttonConfigs"
></redaction-page-header>
<div class="red-content-inner">
<div class="content-container">
@ -46,10 +31,7 @@
<div class="row">
<div translate="license-info-screen.copyright-claim-title"></div>
<div>
{{
'license-info-screen.copyright-claim-text'
| translate: { currentYear: currentYear }
}}
{{ 'license-info-screen.copyright-claim-text' | translate: { currentYear: currentYear } }}
</div>
</div>
@ -64,10 +46,7 @@
<!-- <div>Future feature: we will provide that with the /info endpoint</div>-->
<!-- </div>-->
<div
class="section-title all-caps-label"
translate="license-info-screen.licensing-details"
></div>
<div class="section-title all-caps-label" translate="license-info-screen.licensing-details"></div>
<div class="row">
<div translate="license-info-screen.licensed-to"></div>
@ -92,28 +71,18 @@
<div>{{ currentInfo.numberOfAnalyzedPages }}</div>
</div>
<div
class="section-title all-caps-label"
translate="license-info-screen.usage-details"
></div>
<div class="section-title all-caps-label" translate="license-info-screen.usage-details"></div>
<div class="row">
<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>{{ totalInfo.numberOfAnalyzedPages }}</div>
</div>
<div class="row">
<div translate="license-info-screen.current-analyzed"></div>
<div>
{{ currentInfo.numberOfAnalyzedPages }} ({{
analysisPercentageOfLicense | number: '1.0-2'
}}%)
</div>
<div>{{ currentInfo.numberOfAnalyzedPages }} ({{ analysisPercentageOfLicense | number: '1.0-2' }}%)</div>
</div>
<div *ngIf="!!unlicensedInfo" class="row">
@ -150,7 +119,3 @@
</div>
</div>
</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 * as moment from 'moment';
import { TranslateService } from '@ngx-translate/core';
import { LoadingService } from '../../../../services/loading.service';
import { ButtonConfig } from '../../../shared/components/page-header/models/button-config.model';
import { IconButtonTypes } from '../../../shared/components/buttons/icon-button/icon-button.component';
@Component({
selector: 'redaction-license-information-screen',
@ -16,7 +19,6 @@ export class LicenseInformationScreenComponent implements OnInit {
unlicensedInfo: LicenseReport = {};
totalLicensedNumberOfPages = 0;
analysisPercentageOfLicense = 100;
viewReady = false;
barChart: any[] = [];
lineChartSeries: any[] = [];
yAxisLabel = this._translateService.instant('license-info-screen.chart.pages-per-month');
@ -31,13 +33,23 @@ export class LicenseInformationScreenComponent implements OnInit {
group: 'Ordinal',
domain: ['#0389ec']
};
buttonConfigs: ButtonConfig[] = [
{
label: this._translateService.instant('license-info-screen.email-report'),
action: () => this.sendMail(),
type: IconButtonTypes.PRIMARY
}
];
constructor(
readonly permissionsService: PermissionsService,
readonly appConfigService: AppConfigService,
private readonly _licenseReportController: LicenseReportControllerService,
private readonly _translateService: TranslateService
) {}
private readonly _translateService: TranslateService,
private readonly _loadingService: LoadingService
) {
_loadingService.start();
}
get currentYear(): number {
return new Date().getFullYear();
@ -63,18 +75,15 @@ export class LicenseInformationScreenComponent implements OnInit {
const unlicensedConfig = {
startDate: endDate.toDate()
};
promises.push(
this._licenseReportController.licenseReport(unlicensedConfig).toPromise()
);
promises.push(this._licenseReportController.licenseReport(unlicensedConfig).toPromise());
}
Promise.all(promises).then(reports => {
[this.currentInfo, this.totalInfo, this.unlicensedInfo] = reports;
this.viewReady = true;
this._loadingService.stop();
this.analysisPercentageOfLicense =
this.totalLicensedNumberOfPages > 0
? (this.currentInfo.numberOfAnalyzedPages / this.totalLicensedNumberOfPages) *
100
? (this.currentInfo.numberOfAnalyzedPages / this.totalLicensedNumberOfPages) * 100
: 100;
});
}
@ -92,9 +101,7 @@ export class LicenseInformationScreenComponent implements OnInit {
pages: this.totalLicensedNumberOfPages
})
].join('%0D%0A');
window.location.href = `mailto:${this.appConfigService.getConfig(
'LICENSE_EMAIL'
)}?subject=${subject}&body=${body}`;
window.location.href = `mailto:${this.appConfigService.getConfig('LICENSE_EMAIL')}?subject=${subject}&body=${body}`;
}
private async _setMonthlyStats(startDate: moment.Moment, endDate: moment.Moment) {

View File

@ -39,8 +39,6 @@
<div class="select-oval-placeholder"></div>
<redaction-table-col-name
(toggleSort)="toggleSort($event)"
[activeSortingOption]="sortingOption"
[label]="'trash.table-col-names.name' | translate"
[withSort]="true"
column="name"
@ -50,15 +48,11 @@
class="user-column"
></redaction-table-col-name>
<redaction-table-col-name
(toggleSort)="toggleSort($event)"
[activeSortingOption]="sortingOption"
[label]="'trash.table-col-names.deleted-on' | translate"
[withSort]="true"
column="dateDeleted"
></redaction-table-col-name>
<redaction-table-col-name
(toggleSort)="toggleSort($event)"
[activeSortingOption]="sortingOption"
[label]="'trash.table-col-names.time-to-restore' | translate"
[withSort]="true"
column="timeToRestore"
@ -71,7 +65,7 @@
<redaction-empty-state *ngIf="noMatch$ | async" screen="trash" type="no-match"></redaction-empty-state>
<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">
<redaction-round-checkbox [active]="isSelected(entity)"></redaction-round-checkbox>
</div>

View File

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

View File

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

View File

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

View File

@ -21,8 +21,7 @@
icon="red:assign-me"
tooltip="dossier-overview.assign-me"
type="dark-bg"
>
</redaction-circle-button>
></redaction-circle-button>
<redaction-circle-button
(action)="setToUnderApproval()"
@ -30,8 +29,7 @@
icon="red:ready-for-approval"
tooltip="dossier-overview.under-approval"
type="dark-bg"
>
</redaction-circle-button>
></redaction-circle-button>
<redaction-circle-button
(action)="setToUnderReview()"
@ -39,13 +37,9 @@
icon="red:undo"
tooltip="dossier-overview.under-review"
type="dark-bg"
>
</redaction-circle-button>
></redaction-circle-button>
<redaction-file-download-btn
[dossier]="dossier"
[file]="selectedFiles"
></redaction-file-download-btn>
<redaction-file-download-btn [dossier]="dossier" [file]="selectedFiles"></redaction-file-download-btn>
<!-- Approved-->
<redaction-circle-button
@ -55,8 +49,7 @@
[tooltip]="canApprove ? 'dossier-overview.approve' : 'dossier-overview.approve-disabled'"
icon="red:approved"
type="dark-bg"
>
</redaction-circle-button>
></redaction-circle-button>
<!-- Back to approval -->
<redaction-circle-button
@ -65,8 +58,7 @@
icon="red:undo"
tooltip="dossier-overview.under-approval"
type="dark-bg"
>
</redaction-circle-button>
></redaction-circle-button>
<redaction-circle-button
(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 { UserService } from '@services/user.service';
import { FileManagementControllerService, ReanalysisControllerService } from '@redaction/red-ui-http';
import { PermissionsService } from '@services/permissions.service';
import { FileStatusWrapper } from '@models/file/file-status.wrapper';
import { FileActionService } from '../../services/file-action.service';
import { from, Observable } from 'rxjs';
import { StatusOverlayService } from '@upload-download/services/status-overlay.service';
import { DossiersDialogService } from '../../services/dossiers-dialog.service';
import { LoadingService } from '../../../../services/loading.service';
import { ConfirmationDialogInput } from '../../../shared/dialogs/confirmation-dialog/confirmation-dialog.component';
@ -23,14 +21,11 @@ export class DossierOverviewBulkActionsComponent {
constructor(
private readonly _appStateService: AppStateService,
private readonly _userService: UserService,
private readonly _dialogService: DossiersDialogService,
private readonly _fileManagementControllerService: FileManagementControllerService,
private readonly _reanalysisControllerService: ReanalysisControllerService,
private readonly _permissionsService: PermissionsService,
private readonly _fileActionService: FileActionService,
private readonly _statusOverlayService: StatusOverlayService,
private readonly _changeDetectorRef: ChangeDetectorRef,
private readonly _loadingService: LoadingService,
private readonly _screenStateService: ScreenStateService<FileStatusWrapper>
) {}

View File

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

View File

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

View File

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

View File

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

View File

@ -59,16 +59,12 @@
<div class="select-oval-placeholder"></div>
<redaction-table-col-name
(toggleSort)="toggleSort($event)"
[activeSortingOption]="sortingOption"
[withSort]="true"
column="filename"
label="dossier-overview.table-col-names.name"
></redaction-table-col-name>
<redaction-table-col-name
(toggleSort)="toggleSort($event)"
[activeSortingOption]="sortingOption"
[withSort]="true"
column="added"
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
(toggleSort)="toggleSort($event)"
[activeSortingOption]="sortingOption"
[withSort]="true"
class="user-column"
column="reviewerName"
@ -86,16 +80,12 @@
></redaction-table-col-name>
<redaction-table-col-name
(toggleSort)="toggleSort($event)"
[activeSortingOption]="sortingOption"
[withSort]="true"
column="pages"
label="dossier-overview.table-col-names.pages"
></redaction-table-col-name>
<redaction-table-col-name
(toggleSort)="toggleSort($event)"
[activeSortingOption]="sortingOption"
[withSort]="true"
class="flex-end"
column="statusSort"
@ -116,7 +106,7 @@
<cdk-virtual-scroll-viewport [itemSize]="itemSize" redactionHasScrollbar>
<div
*cdkVirtualFor="let fileStatus of sortedDisplayedEntities$ | async; trackBy: trackByFileId"
*cdkVirtualFor="let fileStatus of sortedDisplayedEntities$ | async; trackBy: trackByPrimaryKey"
[class.disabled]="fileStatus.isExcluded"
[class.last-opened]="isLastOpenedFile(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 { SearchService } from '@shared/services/search.service';
import { ScreenStateService } from '@shared/services/screen-state.service';
import { ScreenNames, SortingService } from '@services/sorting.service';
import { SortingService } from '../../../../services/sorting.service';
import { BaseListingComponent } from '@shared/base/base-listing.component';
import { LoadingService } from '@services/loading.service';
import { DossierAttributesService } from '@shared/services/controller-wrappers/dossier-attributes.service';
@ -42,8 +42,9 @@ export class DossierOverviewScreenComponent
extends BaseListingComponent<FileStatusWrapper>
implements OnInit, OnDestroy, OnDetach, OnAttach
{
collapsedDetails = false;
readonly itemSize = 80;
protected readonly _primaryKey = 'filename';
collapsedDetails = false;
actionConfigs: ActionConfig[];
dossierAttributes: DossierAttributeWithValue[] = [];
@ViewChild(DossierDetailsComponent, { static: false })
@ -73,8 +74,6 @@ export class DossierOverviewScreenComponent
protected readonly _injector: Injector
) {
super(_injector);
this.sortingService.setScreenName(ScreenNames.DOSSIER_OVERVIEW);
this.searchService.setSearchKey('searchField');
this._loadEntitiesFromState();
}
@ -82,8 +81,8 @@ export class DossierOverviewScreenComponent
return this._appStateService.activeDossier;
}
get user() {
return this._userService.user;
get userId() {
return this._userService.userId;
}
get checkedRequiredFilters() {
@ -170,10 +169,6 @@ export class DossierOverviewScreenComponent
this._changeDetectorRef.detectChanges();
}
trackByFileId(index: number, item: FileStatusWrapper) {
return item.fileId;
}
@HostListener('drop', ['$event'])
onDrop(event: DragEvent) {
handleFileDrop(event, this.activeDossier, this._uploadFiles.bind(this));
@ -272,7 +267,7 @@ export class DossierOverviewScreenComponent
slug: 'statusFilters',
label: this._translateService.instant('filters.status'),
icon: 'red:status',
values: statusFilters.sort(StatusSorter.byKey),
values: statusFilters.sort(StatusSorter.byStatus),
checker: keyChecker('status')
});
@ -348,7 +343,7 @@ export class DossierOverviewScreenComponent
{
key: 'assigned-to-me',
label: this._translateService.instant('dossier-overview.quick-filters.assigned-to-me'),
checker: (file: FileStatusWrapper) => file.currentReviewer === this.user.id
checker: (file: FileStatusWrapper) => file.currentReviewer === this.userId
},
{
key: 'unassigned',
@ -358,7 +353,7 @@ export class DossierOverviewScreenComponent
{
key: 'assigned-to-others',
label: this._translateService.instant('dossier-overview.quick-filters.assigned-to-others'),
checker: (file: FileStatusWrapper) => !!file.currentReviewer && file.currentReviewer !== this.user.id
checker: (file: FileStatusWrapper) => !!file.currentReviewer && file.currentReviewer !== this.userId
}
];
}

View File

@ -1,5 +1,5 @@
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 { FilterService } from '../services/filter.service';
import { SearchService } from '../services/search.service';
@ -20,13 +20,30 @@ export abstract class BaseListingComponent<T> extends AutoUnsubscribeComponent i
readonly searchService: SearchService<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) {
super();
this.trackByPrimaryKey = this.trackByPrimaryKey.bind(this);
this.permissionsService = this._injector.get(PermissionsService);
this.filterService = this._injector.get(FilterService);
this.sortingService = this._injector.get(SortingService);
this.searchService = this._injector.get(SearchService);
this.screenStateService = this._injector.get<ScreenStateService<T>>(ScreenStateService);
setTimeout(() => this.setInitialConfig());
}
setInitialConfig() {
this.sortingService.setSortingOption({
column: this._primaryKey,
order: SortingOrders.ASC
});
this.searchService.setSearchKey(this._primaryKey);
}
ngOnDestroy(): void {
@ -45,22 +62,16 @@ export abstract class BaseListingComponent<T> extends AutoUnsubscribeComponent i
return this.screenStateService.allEntities;
}
get sortingOption(): SortingOption {
return this.sortingService.getSortingOption();
}
get noMatch$(): Observable<boolean> {
return combineLatest([this.screenStateService.allEntitiesLength$, this.screenStateService.displayedLength$]).pipe(
map(res => res[0] && !res[1])
map(([hasEntities, hasDisplayedEntities]) => hasEntities && !hasDisplayedEntities)
);
}
canBulkDelete$(hasPermission = true) {
return this.screenStateService.areSomeEntitiesSelected$.pipe(map(res => res && hasPermission));
}
toggleSort($event) {
this.sortingService.toggleSort($event);
return this.screenStateService.areSomeEntitiesSelected$.pipe(
map(areSomeEntitiesSelected => areSomeEntitiesSelected && hasPermission)
);
}
toggleSelectAll() {
@ -75,4 +86,8 @@ export abstract class BaseListingComponent<T> extends AutoUnsubscribeComponent i
isSelected(entity: T): boolean {
return this.screenStateService.isSelected(entity);
}
trackByPrimaryKey(index: number, item: T) {
return item[this._primaryKey];
}
}

View File

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

View File

@ -1,10 +1,11 @@
import { Component, Input } from '@angular/core';
import { PermissionsService } from '@services/permissions.service';
import { Component, Input, Optional } from '@angular/core';
import { ActionConfig } from '@shared/components/page-header/models/action-config.model';
import { ButtonConfig } from '@shared/components/page-header/models/button-config.model';
import { FilterService } from '@shared/services/filter.service';
import { SearchService } from '@shared/services/search.service';
import { map } from 'rxjs/operators';
import { distinctUntilChanged, map } from 'rxjs/operators';
import { combineLatest, Observable } from 'rxjs';
import { FilterWrapper } from '@shared/components/filters/popup-filter/model/filter-wrapper.model';
@Component({
selector: 'redaction-page-header',
@ -18,22 +19,29 @@ export class PageHeaderComponent<T> {
@Input() buttonConfigs: ButtonConfig[];
@Input() searchPlaceholder: string;
constructor(
readonly permissionsService: PermissionsService,
readonly filterService: FilterService<T>,
readonly searchService: SearchService<T>
) {}
constructor(@Optional() readonly filterService: FilterService<T>, @Optional() readonly searchService: SearchService<T>) {}
get filters$() {
return this.filterService.allFilters$.pipe(map(all => all.filter(f => f.icon)));
get filters$(): Observable<FilterWrapper[]> {
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.searchService.reset();
}
trackByLabel(index: number, item) {
trackByLabel<K extends { label?: string }>(index: number, item: K): string {
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>
<span [translate]="label" class="all-caps-label"></span>
<mat-icon
*ngIf="!!rightIcon"
[matTooltip]="rightIconTooltip | translate"
[svgIcon]="rightIcon"
matTooltipPosition="above"
></mat-icon>
<mat-icon *ngIf="!!rightIcon" [matTooltip]="rightIconTooltip | translate" [svgIcon]="rightIcon" matTooltipPosition="above"></mat-icon>
<div
*ngIf="withSort"
[class.force-display]="activeSortingOption.column === column"
*ngIf="withSort && sortingService"
[class.force-display]="sortingService.sortingOption?.column === column"
class="sort-arrows-container"
>
<mat-icon *ngIf="activeSortingOption?.order === 'asc'" svgIcon="red:sort-asc"></mat-icon>
<mat-icon *ngIf="activeSortingOption?.order === 'desc'" svgIcon="red:sort-desc"></mat-icon>
<mat-icon *ngIf="sortingService.sortingOption?.order === 'asc'" svgIcon="red:sort-asc"></mat-icon>
<mat-icon *ngIf="sortingService.sortingOption?.order === 'desc'" svgIcon="red:sort-desc"></mat-icon>
</div>
</div>

View File

@ -1,5 +1,5 @@
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { SortingOption } from '@services/sorting.service';
import { Component, Input, Optional } from '@angular/core';
import { SortingService } from '@services/sorting.service';
@Component({
selector: 'redaction-table-col-name',
@ -7,7 +7,6 @@ import { SortingOption } from '@services/sorting.service';
styleUrls: ['./table-col-name.component.scss']
})
export class TableColNameComponent {
@Input() activeSortingOption: SortingOption;
@Input() column: string;
@Input() label: string;
@Input() withSort = false;
@ -16,5 +15,5 @@ export class TableColNameComponent {
@Input() rightIcon: 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$() {
return this.allFilters$.pipe(
map(all => this._toFlatFilters(all)),
filter(f => !!f.find(el => el.checked)),
map(f => !!f.find(el => el.checked)),
distinctUntilChanged()
);
}

View File

@ -74,7 +74,9 @@ export class ScreenStateService<T> {
}
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
*/
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 {

View File

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

View File

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

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 = {
ERROR: 0,
UNPROCESSED: 1,
@ -9,5 +19,5 @@ export const StatusSorter = {
UNDER_REVIEW: 15,
UNDER_APPROVAL: 20,
APPROVED: 25,
byKey: (a: { key?: string }, b: { key?: string }) => StatusSorter[a.key] - StatusSorter[b.key]
byStatus: byStatus
};