change base listing component

This commit is contained in:
Dan Percic 2021-07-14 15:30:06 +03:00
parent 6c21b6e957
commit bb53a1a6dc
18 changed files with 147 additions and 311 deletions

View File

@ -3,7 +3,7 @@
<redaction-round-checkbox <redaction-round-checkbox
(click)="toggleSelectAll()" (click)="toggleSelectAll()"
[active]="areAllEntitiesSelected" [active]="areAllEntitiesSelected"
[indeterminate]="areSomeEntitiesSelected && !areAllEntitiesSelected" [indeterminate]="(areSomeEntitiesSelected$ | async) && !areAllEntitiesSelected"
></redaction-round-checkbox> ></redaction-round-checkbox>
</div> </div>
<span class="all-caps-label"> <span class="all-caps-label">
@ -13,22 +13,20 @@
}} }}
</span> </span>
<ng-container *ngIf="areSomeEntitiesSelected"> <ng-container *ngIf="areSomeEntitiesSelected$ | async">
<redaction-circle-button <redaction-circle-button
[matMenuTriggerFor]="readOnlyMenu" [matMenuTriggerFor]="readOnlyMenu"
icon="red:read-only" icon="red:read-only"
tooltip="file-attributes-csv-import.table-header.actions.read-only" tooltip="file-attributes-csv-import.table-header.actions.read-only"
type="dark-bg" type="dark-bg"
> ></redaction-circle-button>
</redaction-circle-button>
<redaction-circle-button <redaction-circle-button
(action)="deactivateSelection()" (action)="deactivateSelection()"
icon="red:trash" icon="red:trash"
tooltip="file-attributes-csv-import.table-header.actions.remove-selected" tooltip="file-attributes-csv-import.table-header.actions.remove-selected"
type="dark-bg" type="dark-bg"
> ></redaction-circle-button>
</redaction-circle-button>
<div class="separator"></div> <div class="separator"></div>
@ -92,7 +90,7 @@
</div> </div>
<redaction-empty-state <redaction-empty-state
*ngIf="!allEntities.length" *ngIf="(allEntities$ | async)?.length === 0"
icon="red:attribute" icon="red:attribute"
screen="file-attributes-csv-import" screen="file-attributes-csv-import"
></redaction-empty-state> ></redaction-empty-state>
@ -102,7 +100,7 @@
<div <div
(mouseenter)="setHoveredColumn.emit(field.csvColumn)" (mouseenter)="setHoveredColumn.emit(field.csvColumn)"
(mouseleave)="setHoveredColumn.emit()" (mouseleave)="setHoveredColumn.emit()"
*cdkVirtualFor="let field of displayedEntities" *cdkVirtualFor="let field of displayedEntities$ | async"
class="table-item" class="table-item"
> >
<div (click)="toggleEntitySelected($event, field)" class="selection-column"> <div (click)="toggleEntitySelected($event, field)" class="selection-column">
@ -127,23 +125,20 @@
icon="red:edit" icon="red:edit"
tooltip="file-attributes-csv-import.action.edit-name" tooltip="file-attributes-csv-import.action.edit-name"
type="dark-bg" type="dark-bg"
> ></redaction-circle-button>
</redaction-circle-button>
<ng-container *ngIf="field.editingName"> <ng-container *ngIf="field.editingName">
<redaction-circle-button <redaction-circle-button
(action)="field.editingName = false; field.name = field.temporaryName" (action)="field.editingName = false; field.name = field.temporaryName"
icon="red:check" icon="red:check"
tooltip="file-attributes-csv-import.action.save-name" tooltip="file-attributes-csv-import.action.save-name"
type="dark-bg" type="dark-bg"
> ></redaction-circle-button>
</redaction-circle-button>
<redaction-circle-button <redaction-circle-button
(action)="field.editingName = false; field.temporaryName = field.name" (action)="field.editingName = false; field.temporaryName = field.name"
icon="red:close" icon="red:close"
tooltip="file-attributes-csv-import.action.cancel-edit-name" tooltip="file-attributes-csv-import.action.cancel-edit-name"
type="dark-bg" type="dark-bg"
> ></redaction-circle-button>
</redaction-circle-button>
</ng-container> </ng-container>
</div> </div>
<div> <div>
@ -174,8 +169,7 @@
icon="red:trash" icon="red:trash"
tooltip="file-attributes-csv-import.action.remove" tooltip="file-attributes-csv-import.action.remove"
type="dark-bg" type="dark-bg"
> ></redaction-circle-button>
</redaction-circle-button>
</div> </div>
</div> </div>
<div class="scrollbar-placeholder"></div> <div class="scrollbar-placeholder"></div>

View File

@ -7,13 +7,13 @@ import {
Output, Output,
SimpleChanges SimpleChanges
} from '@angular/core'; } from '@angular/core';
import { BaseListingComponent } from '@shared/base/base-listing.component';
import { Field } from '../file-attributes-csv-import-dialog.component'; import { Field } from '../file-attributes-csv-import-dialog.component';
import { FileAttributeConfig } from '@redaction/red-ui-http'; import { FileAttributeConfig } 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 { SortingService } from '../../../../../services/sorting.service'; import { SortingService } from '../../../../../services/sorting.service';
import { BaseListingComponent } from '../../../../shared/base/base-listing.component';
@Component({ @Component({
selector: 'redaction-active-fields-listing', selector: 'redaction-active-fields-listing',
@ -22,8 +22,8 @@ import { SortingService } from '../../../../../services/sorting.service';
providers: [FilterService, SearchService, ScreenStateService, SortingService] providers: [FilterService, SearchService, ScreenStateService, SortingService]
}) })
export class ActiveFieldsListingComponent extends BaseListingComponent<Field> implements OnChanges { export class ActiveFieldsListingComponent extends BaseListingComponent<Field> implements OnChanges {
@Input() allEntities: Field[]; @Input() entities: Field[];
@Output() allEntitiesChange = new EventEmitter<Field[]>(); @Output() entitiesChange = new EventEmitter<Field[]>();
@Output() setHoveredColumn = new EventEmitter<string>(); @Output() setHoveredColumn = new EventEmitter<string>();
@Output() toggleFieldActive = new EventEmitter<Field>(); @Output() toggleFieldActive = new EventEmitter<Field>();
@ -33,16 +33,16 @@ export class ActiveFieldsListingComponent extends BaseListingComponent<Field> im
FileAttributeConfig.TypeEnum.DATE FileAttributeConfig.TypeEnum.DATE
]; ];
protected readonly _selectionKey = 'csvColumn';
constructor(protected readonly _injector: Injector) { constructor(protected readonly _injector: Injector) {
super(_injector); super(_injector);
this._screenStateService.setIdKey('csvColumn');
} }
ngOnChanges(changes: SimpleChanges): void { ngOnChanges(changes: SimpleChanges): void {
if (changes.allEntities) { if (changes.entities) {
this.displayedEntities = this.allEntities; this._screenStateService.setEntities(this.entities);
this._updateSelection(); this._screenStateService.setDisplayedEntities(this.entities);
this._screenStateService.updateSelection();
} }
} }
@ -50,13 +50,15 @@ export class ActiveFieldsListingComponent extends BaseListingComponent<Field> im
this.allEntities this.allEntities
.filter(field => this.isSelected(field)) .filter(field => this.isSelected(field))
.forEach(field => (field.primaryAttribute = false)); .forEach(field => (field.primaryAttribute = false));
this.allEntities = [...this.allEntities.filter(field => !this.isSelected(field))]; this._screenStateService.setEntities([
this.allEntitiesChange.emit(this.allEntities); ...this.allEntities.filter(field => !this.isSelected(field))
this.selectedEntitiesIds = []; ]);
this.entitiesChange.emit(this.allEntities);
this._screenStateService.setSelectedEntitiesIds([]);
} }
setAttributeForSelection(attribute: string, value: any) { setAttributeForSelection(attribute: string, value: any) {
for (const csvColumn of this.selectedEntitiesIds) { for (const csvColumn of this._screenStateService.selectedEntitiesIds) {
this.allEntities.find(f => f.csvColumn === csvColumn)[attribute] = value; this.allEntities.find(f => f.csvColumn === csvColumn)[attribute] = value;
} }
} }

View File

@ -119,7 +119,7 @@
(click)="toggleFieldActive(field)" (click)="toggleFieldActive(field)"
(mouseenter)="setHoveredColumn(field.csvColumn)" (mouseenter)="setHoveredColumn(field.csvColumn)"
(mouseleave)="setHoveredColumn()" (mouseleave)="setHoveredColumn()"
*ngFor="let field of displayedEntities" *ngFor="let field of displayedEntities$ | async"
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">
@ -177,7 +177,7 @@
<redaction-active-fields-listing <redaction-active-fields-listing
(setHoveredColumn)="setHoveredColumn($event)" (setHoveredColumn)="setHoveredColumn($event)"
(toggleFieldActive)="toggleFieldActive($event)" (toggleFieldActive)="toggleFieldActive($event)"
[(allEntities)]="activeFields" [(entities)]="activeFields"
></redaction-active-fields-listing> ></redaction-active-fields-listing>
</div> </div>
</div> </div>

View File

@ -1,5 +1,5 @@
import { Component, Inject, Injector } from '@angular/core'; import { Component, Inject, Injector } from '@angular/core';
import { AbstractControl, FormGroup, ValidatorFn, Validators } from '@angular/forms'; import { AbstractControl, FormBuilder, FormGroup, ValidatorFn, Validators } from '@angular/forms';
import { AppStateService } from '@state/app-state.service'; import { AppStateService } from '@state/app-state.service';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import * as Papa from 'papaparse'; import * as Papa from 'papaparse';
@ -10,13 +10,13 @@ import {
} from '@redaction/red-ui-http'; } from '@redaction/red-ui-http';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { map, startWith } from 'rxjs/operators'; import { map, startWith } from 'rxjs/operators';
import { BaseListingComponent } from '@shared/base/base-listing.component';
import { NotificationService, NotificationType } from '@services/notification.service'; import { NotificationService, NotificationType } from '@services/notification.service';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
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 { SortingService } from '../../../../services/sorting.service'; import { SortingService } from '../../../../services/sorting.service';
import { BaseListingComponent } from '../../../shared/base/base-listing.component';
export interface Field { export interface Field {
id?: string; id?: string;
@ -55,6 +55,7 @@ export class FileAttributesCsvImportDialogComponent extends BaseListingComponent
private readonly _fileAttributesControllerService: FileAttributesControllerService, private readonly _fileAttributesControllerService: FileAttributesControllerService,
private readonly _translateService: TranslateService, private readonly _translateService: TranslateService,
private readonly _notificationService: NotificationService, private readonly _notificationService: NotificationService,
private readonly _formBuilder: FormBuilder,
public dialogRef: MatDialogRef<FileAttributesCsvImportDialogComponent>, public dialogRef: MatDialogRef<FileAttributesCsvImportDialogComponent>,
protected readonly _injector: Injector, protected readonly _injector: Injector,
@Inject(MAT_DIALOG_DATA) @Inject(MAT_DIALOG_DATA)
@ -102,10 +103,10 @@ export class FileAttributesCsvImportDialogComponent extends BaseListingComponent
this.parseResult.meta.fields = Object.keys(this.parseResult.data[0]); this.parseResult.meta.fields = Object.keys(this.parseResult.data[0]);
} }
this.allEntities = this.parseResult.meta.fields.map(field => this._screenStateService.setEntities(
this._buildAttribute(field) this.parseResult.meta.fields.map(field => this._buildAttribute(field))
); );
this.displayedEntities = [...this.allEntities]; this._screenStateService.setDisplayedEntities(this.allEntities);
this.activeFields = []; this.activeFields = [];
for (const entity of this.allEntities) { for (const entity of this.allEntities) {
@ -175,9 +176,9 @@ export class FileAttributesCsvImportDialogComponent extends BaseListingComponent
} }
} }
return count; return count;
} else {
return 0;
} }
return 0;
} }
isActive(field: Field): boolean { isActive(field: Field): boolean {

View File

@ -24,7 +24,7 @@
<span class="all-caps-label"> <span class="all-caps-label">
{{ {{
'default-colors-screen.table-header.title' 'default-colors-screen.table-header.title'
| translate: { length: allEntities.length } | translate: { length: (allEntities$ | async).length }
}} }}
</span> </span>
</div> </div>
@ -51,7 +51,9 @@
<!-- Table lines --> <!-- Table lines -->
<div <div
*cdkVirtualFor=" *cdkVirtualFor="
let color of allEntities | sortBy: sortingOption.order:sortingOption.column let color of allEntities$
| async
| sortBy: sortingOption.order:sortingOption.column
" "
class="table-item" class="table-item"
> >
@ -77,8 +79,7 @@
icon="red:edit" icon="red:edit"
tooltip="default-colors-screen.action.edit" tooltip="default-colors-screen.action.edit"
type="dark-bg" type="dark-bg"
> ></redaction-circle-button>
</redaction-circle-button>
</div> </div>
</div> </div>
<div class="scrollbar-placeholder"></div> <div class="scrollbar-placeholder"></div>

View File

@ -4,12 +4,12 @@ import { Colors, DictionaryControllerService } from '@redaction/red-ui-http';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { PermissionsService } from '@services/permissions.service'; import { PermissionsService } from '@services/permissions.service';
import { AdminDialogService } from '../../services/admin-dialog.service'; import { AdminDialogService } from '../../services/admin-dialog.service';
import { BaseListingComponent } from '@shared/base/base-listing.component';
import { LoadingService } from '../../../../services/loading.service'; 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 { ScreenNames, SortingService } from '../../../../services/sorting.service';
import { BaseListingComponent } from '../../../shared/base/base-listing.component';
@Component({ @Component({
templateUrl: './default-colors-screen.component.html', templateUrl: './default-colors-screen.component.html',
@ -64,10 +64,12 @@ export class DefaultColorsScreenComponent
.getColors(this._appStateService.activeDossierTemplateId) .getColors(this._appStateService.activeDossierTemplateId)
.toPromise(); .toPromise();
this._colorsObj = data; this._colorsObj = data;
this.allEntities = Object.keys(data).map(key => ({ this._screenStateService.setEntities(
key, Object.keys(data).map(key => ({
value: data[key] key,
})); value: data[key]
}))
);
this._loadingService.stop(); this._loadingService.stop();
} }
} }

View File

@ -7,7 +7,6 @@ import { forkJoin, of } from 'rxjs';
import { PermissionsService } from '@services/permissions.service'; import { PermissionsService } from '@services/permissions.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 { BaseListingComponent } from '@shared/base/base-listing.component';
import { TypeValueWrapper } from '../../../../models/file/type-value.wrapper'; import { TypeValueWrapper } from '../../../../models/file/type-value.wrapper';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { LoadingService } from '../../../../services/loading.service'; import { LoadingService } from '../../../../services/loading.service';
@ -15,7 +14,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 { ScreenNames, SortingService } from '../../../../services/sorting.service'; import { ScreenNames, SortingService } from '../../../../services/sorting.service';
import { NewBaseListingComponent } from '../../../shared/base/new-base-listing.component'; import { BaseListingComponent } from '../../../shared/base/base-listing.component';
const toChartConfig = (dict: TypeValueWrapper): DoughnutChartConfig => ({ const toChartConfig = (dict: TypeValueWrapper): DoughnutChartConfig => ({
value: dict.entries ? dict.entries.length : 0, value: dict.entries ? dict.entries.length : 0,
@ -30,7 +29,7 @@ const toChartConfig = (dict: TypeValueWrapper): DoughnutChartConfig => ({
providers: [FilterService, SearchService, ScreenStateService, SortingService] providers: [FilterService, SearchService, ScreenStateService, SortingService]
}) })
export class DictionaryListingScreenComponent export class DictionaryListingScreenComponent
extends NewBaseListingComponent<TypeValueWrapper> extends BaseListingComponent<TypeValueWrapper>
implements OnInit implements OnInit
{ {
chartData: DoughnutChartConfig[] = []; chartData: DoughnutChartConfig[] = [];

View File

@ -10,7 +10,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 { ScreenNames, SortingService } from '../../../../services/sorting.service'; import { ScreenNames, SortingService } from '../../../../services/sorting.service';
import { NewBaseListingComponent } from '../../../shared/base/new-base-listing.component'; import { BaseListingComponent } from '../../../shared/base/base-listing.component';
@Component({ @Component({
templateUrl: './dossier-templates-listing-screen.component.html', templateUrl: './dossier-templates-listing-screen.component.html',
@ -19,7 +19,7 @@ import { NewBaseListingComponent } from '../../../shared/base/new-base-listing.c
providers: [FilterService, SearchService, ScreenStateService, SortingService] providers: [FilterService, SearchService, ScreenStateService, SortingService]
}) })
export class DossierTemplatesListingScreenComponent export class DossierTemplatesListingScreenComponent
extends NewBaseListingComponent<DossierTemplateModelWrapper> extends BaseListingComponent<DossierTemplateModelWrapper>
implements OnInit implements OnInit
{ {
constructor( constructor(

View File

@ -25,17 +25,22 @@
<redaction-round-checkbox <redaction-round-checkbox
(click)="toggleSelectAll()" (click)="toggleSelectAll()"
[active]="areAllEntitiesSelected" [active]="areAllEntitiesSelected"
[indeterminate]="areSomeEntitiesSelected && !areAllEntitiesSelected" [indeterminate]="
(areSomeEntitiesSelected$ | async) && !areAllEntitiesSelected
"
></redaction-round-checkbox> ></redaction-round-checkbox>
</div> </div>
<span class="all-caps-label"> <span class="all-caps-label">
{{ 'file-attributes-listing.table-header.title' | translate: { length: displayedEntities.length } }} {{
'file-attributes-listing.table-header.title'
| translate: { length: (displayedEntities$ | async)?.length }
}}
</span> </span>
<redaction-circle-button <redaction-circle-button
(click)="openConfirmDeleteAttributeDialog($event)" (click)="openConfirmDeleteAttributeDialog($event)"
*ngIf="areSomeEntitiesSelected" *ngIf="areSomeEntitiesSelected$ | 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"
@ -67,7 +72,11 @@
</div> </div>
</div> </div>
<div [class.no-data]="!allEntities.length" class="table-header" redactionSyncWidth="table-item"> <div
[class.no-data]="(allEntities$ | async)?.length === 0"
class="table-header"
redactionSyncWidth="table-item"
>
<div class="select-oval-placeholder"></div> <div class="select-oval-placeholder"></div>
<redaction-table-col-name <redaction-table-col-name
@ -110,13 +119,13 @@
</div> </div>
<redaction-empty-state <redaction-empty-state
*ngIf="!allEntities.length" *ngIf="(allEntities$ | async)?.length === 0"
icon="red:attribute" icon="red:attribute"
screen="file-attributes-listing" screen="file-attributes-listing"
></redaction-empty-state> ></redaction-empty-state>
<redaction-empty-state <redaction-empty-state
*ngIf="allEntities.length && !displayedEntities.length" *ngIf="(allEntities$ | async)?.length && (displayedEntities$ | async)?.length === 0"
screen="file-attributes-listing" screen="file-attributes-listing"
type="no-match" type="no-match"
></redaction-empty-state> ></redaction-empty-state>
@ -124,7 +133,11 @@
<cdk-virtual-scroll-viewport [itemSize]="80" redactionHasScrollbar> <cdk-virtual-scroll-viewport [itemSize]="80" redactionHasScrollbar>
<!-- Table lines --> <!-- Table lines -->
<div <div
*cdkVirtualFor="let attribute of displayedEntities | sortBy: sortingOption.order:sortingOption.column" *cdkVirtualFor="
let attribute of displayedEntities$
| async
| sortBy: sortingOption.order:sortingOption.column
"
class="table-item" class="table-item"
> >
<div (click)="toggleEntitySelected($event, attribute)" class="selection-column"> <div (click)="toggleEntitySelected($event, attribute)" class="selection-column">

View File

@ -1,26 +1,34 @@
import { Component, ElementRef, Injector, OnInit, ViewChild } from '@angular/core'; import {
ChangeDetectionStrategy,
Component,
ElementRef,
Injector,
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';
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 { BaseListingComponent } from '@shared/base/base-listing.component';
import { LoadingService } from '../../../../services/loading.service'; 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 { ScreenNames, SortingService } from '../../../../services/sorting.service';
import { BaseListingComponent } from '../../../shared/base/base-listing.component';
@Component({ @Component({
templateUrl: './file-attributes-listing-screen.component.html', templateUrl: './file-attributes-listing-screen.component.html',
styleUrls: ['./file-attributes-listing-screen.component.scss'], styleUrls: ['./file-attributes-listing-screen.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [FilterService, SearchService, ScreenStateService, SortingService] providers: [FilterService, SearchService, ScreenStateService, SortingService]
}) })
export class FileAttributesListingScreenComponent extends BaseListingComponent<FileAttributeConfig> implements OnInit { export class FileAttributesListingScreenComponent
protected readonly _searchKey = 'label'; extends BaseListingComponent<FileAttributeConfig>
protected readonly _selectionKey = 'id'; implements OnInit
{
private _existingConfiguration: FileAttributesConfig; private _existingConfiguration: FileAttributesConfig;
@ViewChild('fileInput') private _fileInput: ElementRef; @ViewChild('fileInput') private _fileInput: ElementRef;
constructor( constructor(
@ -34,9 +42,9 @@ export class FileAttributesListingScreenComponent extends BaseListingComponent<F
) { ) {
super(_injector); super(_injector);
this._sortingService.setScreenName(ScreenNames.FILE_ATTRIBUTES_LISTING); this._sortingService.setScreenName(ScreenNames.FILE_ATTRIBUTES_LISTING);
this._appStateService.activateDossierTemplate( this._searchService.setSearchKey('label');
_activatedRoute.snapshot.params.dossierTemplateId this._screenStateService.setIdKey('id');
); _appStateService.activateDossierTemplate(_activatedRoute.snapshot.params.dossierTemplateId);
} }
async ngOnInit() { async ngOnInit() {
@ -67,7 +75,10 @@ export class FileAttributesListingScreenComponent extends BaseListingComponent<F
.toPromise(); .toPromise();
} else { } else {
await this._fileAttributesService await this._fileAttributesService
.deleteFileAttributes(this.selectedEntitiesIds, this._appStateService.activeDossierTemplateId) .deleteFileAttributes(
this._screenStateService.selectedEntitiesIds,
this._appStateService.activeDossierTemplateId
)
.toPromise(); .toPromise();
} }
await this._loadData(); await this._loadData();
@ -86,9 +97,7 @@ export class FileAttributesListingScreenComponent extends BaseListingComponent<F
dossierTemplateId: this._appStateService.activeDossierTemplateId, dossierTemplateId: this._appStateService.activeDossierTemplateId,
existingConfiguration: this._existingConfiguration existingConfiguration: this._existingConfiguration
}, },
async () => { async () => await this._loadData()
await this._loadData();
}
); );
} }
@ -99,10 +108,10 @@ export class FileAttributesListingScreenComponent extends BaseListingComponent<F
.getFileAttributesConfiguration(this._appStateService.activeDossierTemplateId) .getFileAttributesConfiguration(this._appStateService.activeDossierTemplateId)
.toPromise(); .toPromise();
this._existingConfiguration = response; this._existingConfiguration = response;
this.allEntities = response?.fileAttributeConfigs || []; this._screenStateService.setEntities(response?.fileAttributeConfigs || []);
} catch (e) { } catch (e) {
} finally { } finally {
this._executeSearchImmediately(); this.filterService.filterEntities();
this._loadingService.stop(); this._loadingService.stop();
} }
} }

View File

@ -114,12 +114,6 @@
> >
{{ entity.dossierName }} {{ entity.dossierName }}
</div> </div>
<div class="small-label stats-subtitle">
<div>
<mat-icon svgIcon="red:template"></mat-icon>
{{ getDossierTemplate(entity.dossierTemplateId).name }}
</div>
</div>
<div class="small-label stats-subtitle"> <div class="small-label stats-subtitle">
<div> <div>
<mat-icon svgIcon="red:user"></mat-icon> <mat-icon svgIcon="red:user"></mat-icon>
@ -144,7 +138,7 @@
</div> </div>
<div class="small-label"> <div class="small-label">
{{ entity.softDeletedTime | date: 'd MMM. yyyy hh:mm' }} {{ entity.softDeletedTime | date: 'd MMM. yyyy, hh:mm a' }}
</div> </div>
<div> <div>

View File

@ -30,7 +30,7 @@ redaction-table-col-name::ng-deep {
} }
> div { > div {
height: 90px; height: 80px;
padding: 0 24px; padding: 0 24px;
} }
} }

View File

@ -1,7 +1,7 @@
import { ChangeDetectionStrategy, 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 { PermissionsService } from '@services/permissions.service'; import { PermissionsService } from '@services/permissions.service';
import { Dossier, DossierTemplateModel } from '@redaction/red-ui-http'; import { Dossier, DossierTemplateModel, StatusControllerService } from '@redaction/red-ui-http';
import { LoadingService } from '../../../../services/loading.service'; import { LoadingService } from '../../../../services/loading.service';
import { AppConfigKey, AppConfigService } from '../../../app-config/app-config.service'; import { AppConfigKey, AppConfigService } from '../../../app-config/app-config.service';
import * as moment from 'moment'; import * as moment from 'moment';
@ -9,8 +9,9 @@ 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 { ScreenNames, SortingService } from '../../../../services/sorting.service';
import { NewBaseListingComponent } from '../../../shared/base/new-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';
import { tap } from 'rxjs/operators';
@Component({ @Component({
templateUrl: './trash-screen.component.html', templateUrl: './trash-screen.component.html',
@ -18,7 +19,7 @@ import { DossiersService } from '../../../dossier/services/dossiers.service';
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
providers: [FilterService, SearchService, ScreenStateService, SortingService, DossiersService] providers: [FilterService, SearchService, ScreenStateService, SortingService, DossiersService]
}) })
export class TrashScreenComponent extends NewBaseListingComponent<Dossier> implements OnInit { export class TrashScreenComponent extends BaseListingComponent<Dossier> implements OnInit {
readonly itemSize = 85; readonly itemSize = 85;
private readonly _deleteRetentionHours = this._appConfigService.getConfig( private readonly _deleteRetentionHours = this._appConfigService.getConfig(
AppConfigKey.DELETE_RETENTION_HOURS AppConfigKey.DELETE_RETENTION_HOURS
@ -26,6 +27,7 @@ export class TrashScreenComponent extends NewBaseListingComponent<Dossier> imple
constructor( constructor(
private readonly _appStateService: AppStateService, private readonly _appStateService: AppStateService,
private readonly _statusControllerService: StatusControllerService,
readonly permissionsService: PermissionsService, readonly permissionsService: PermissionsService,
protected readonly _injector: Injector, protected readonly _injector: Injector,
private readonly _dossiersService: DossiersService, private readonly _dossiersService: DossiersService,
@ -50,10 +52,6 @@ export class TrashScreenComponent extends NewBaseListingComponent<Dossier> imple
this._screenStateService.setEntities(await this._dossiersService.getDeletedDossiers()); this._screenStateService.setEntities(await this._dossiersService.getDeletedDossiers());
} }
getDossierTemplate(dossierTemplateId: string): DossierTemplateModel {
return this._appStateService.getDossierTemplateById(dossierTemplateId);
}
getRestoreDate(softDeletedTime: string): string { getRestoreDate(softDeletedTime: string): string {
return moment(softDeletedTime).add(this._deleteRetentionHours, 'hours').toISOString(); return moment(softDeletedTime).add(this._deleteRetentionHours, 'hours').toISOString();
} }

View File

@ -19,7 +19,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 { SortingService } from '../../../../services/sorting.service'; import { SortingService } from '../../../../services/sorting.service';
import { NewBaseListingComponent } from '../../../shared/base/new-base-listing.component'; import { BaseListingComponent } from '../../../shared/base/base-listing.component';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { map } from 'rxjs/operators'; import { map } from 'rxjs/operators';
@ -29,7 +29,7 @@ import { map } from 'rxjs/operators';
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
providers: [FilterService, SearchService, ScreenStateService, SortingService] providers: [FilterService, SearchService, ScreenStateService, SortingService]
}) })
export class UserListingScreenComponent extends NewBaseListingComponent<User> implements OnInit { export class UserListingScreenComponent extends BaseListingComponent<User> implements OnInit {
collapsedDetails = false; collapsedDetails = false;
chartData: DoughnutChartConfig[] = []; chartData: DoughnutChartConfig[] = [];
@ViewChildren(InitialsAvatarComponent) @ViewChildren(InitialsAvatarComponent)

View File

@ -35,7 +35,7 @@ import { ButtonConfig } from '../../../shared/components/page-header/models/butt
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 { NewBaseListingComponent } from '../../../shared/base/new-base-listing.component'; import { BaseListingComponent } from '../../../shared/base/base-listing.component';
import { ScreenNames, SortingService } from '../../../../services/sorting.service'; import { ScreenNames, SortingService } from '../../../../services/sorting.service';
@Component({ @Component({
@ -45,7 +45,7 @@ import { ScreenNames, SortingService } from '../../../../services/sorting.servic
providers: [FilterService, SearchService, ScreenStateService, SortingService] providers: [FilterService, SearchService, ScreenStateService, SortingService]
}) })
export class DossierListingScreenComponent export class DossierListingScreenComponent
extends NewBaseListingComponent<DossierWrapper> extends BaseListingComponent<DossierWrapper>
implements OnInit, OnDestroy, OnAttach, OnDetach implements OnInit, OnDestroy, OnAttach, OnDetach
{ {
dossiersChartData: DoughnutChartConfig[] = []; dossiersChartData: DoughnutChartConfig[] = [];

View File

@ -43,7 +43,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 { ScreenNames, SortingService } from '../../../../services/sorting.service'; import { ScreenNames, SortingService } from '../../../../services/sorting.service';
import { NewBaseListingComponent } from '../../../shared/base/new-base-listing.component'; import { BaseListingComponent } from '../../../shared/base/base-listing.component';
@Component({ @Component({
templateUrl: './dossier-overview-screen.component.html', templateUrl: './dossier-overview-screen.component.html',
@ -51,7 +51,7 @@ import { NewBaseListingComponent } from '../../../shared/base/new-base-listing.c
providers: [FilterService, SearchService, ScreenStateService, SortingService] providers: [FilterService, SearchService, ScreenStateService, SortingService]
}) })
export class DossierOverviewScreenComponent export class DossierOverviewScreenComponent
extends NewBaseListingComponent<FileStatusWrapper> extends BaseListingComponent<FileStatusWrapper>
implements OnInit, OnDestroy, OnDetach, OnAttach implements OnInit, OnDestroy, OnDetach, OnAttach
{ {
collapsedDetails = false; collapsedDetails = false;

View File

@ -1,177 +1,83 @@
import { ChangeDetectorRef, Component, Injector, ViewChild } from '@angular/core'; import { Component, Injector, ViewChild } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';
import { SortingOption, SortingService } from '@services/sorting.service'; import { SortingOption, SortingService } from '@services/sorting.service';
import { FilterModel } from '../components/filters/popup-filter/model/filter.model';
import { QuickFiltersComponent } from '../components/filters/quick-filters/quick-filters.component';
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';
import { ScreenStateService } from '../services/screen-state.service'; import { ScreenStateService } from '../services/screen-state.service';
import { getFilteredEntities } from '../components/filters/popup-filter/utils/filter-utils'; import { Observable } from 'rxjs';
import { debounce } from '../../../utils/debounce'; import { FilterModel } from '../components/filters/popup-filter/model/filter.model';
import { FilterWrapper } from '../components/filters/popup-filter/model/filter-wrapper.model';
// Functionalities: Filter, search, select, sort
// Usage: overwrite necessary methods/members in your component
@Component({ template: '' }) @Component({ template: '' })
export abstract class BaseListingComponent<T = any> { export abstract class BaseListingComponent<T> {
allEntities: T[] = []; @ViewChild(CdkVirtualScrollViewport)
filteredEntities: T[] = []; readonly scrollViewport: CdkVirtualScrollViewport;
displayedEntities: T[] = [];
selectedEntitiesIds: string[] = [];
searchForm: FormGroup;
showResetFilters = false;
@ViewChild(CdkVirtualScrollViewport) scrollViewport: CdkVirtualScrollViewport;
protected readonly _formBuilder: FormBuilder; readonly filterService: FilterService<T>;
protected readonly _changeDetectorRef: ChangeDetectorRef;
protected readonly _sortingService: SortingService; protected readonly _sortingService: SortingService;
protected readonly _filterService: FilterService<T>;
protected readonly _searchService: SearchService<T>; protected readonly _searchService: SearchService<T>;
protected readonly _screenStateService: ScreenStateService<T>; protected readonly _screenStateService: ScreenStateService<T>;
// ----
// Overwrite in child class:
protected readonly _searchKey: string;
protected readonly _selectionKey: string;
// Overwrite this in ngOnInit
@ViewChild(QuickFiltersComponent)
protected _quickFilters: QuickFiltersComponent<T>;
private _searchValue = '';
protected constructor(protected readonly _injector: Injector) { protected constructor(protected readonly _injector: Injector) {
this._formBuilder = this._injector.get<FormBuilder>(FormBuilder); this.filterService = this._injector.get<FilterService<T>>(FilterService);
this._changeDetectorRef = this._injector.get<ChangeDetectorRef>(ChangeDetectorRef);
this._sortingService = this._injector.get<SortingService>(SortingService); this._sortingService = this._injector.get<SortingService>(SortingService);
this._filterService = this._injector.get<FilterService<T>>(FilterService);
this._searchService = this._injector.get<SearchService<T>>(SearchService); this._searchService = this._injector.get<SearchService<T>>(SearchService);
this._screenStateService = this._injector.get<ScreenStateService<T>>(ScreenStateService); this._screenStateService = this._injector.get<ScreenStateService<T>>(ScreenStateService);
} }
get areAllEntitiesSelected() { get selectedEntitiesIds$(): Observable<string[]> {
return ( return this._screenStateService.selectedEntitiesIds$;
this.displayedEntities.length !== 0 &&
this.selectedEntitiesIds.length === this.displayedEntities.length
);
} }
get areSomeEntitiesSelected() { get displayedEntities$(): Observable<T[]> {
return this.selectedEntitiesIds.length > 0; return this._screenStateService.displayedEntities$;
}
get allEntities$(): Observable<T[]> {
return this._screenStateService.entities$;
}
get allEntities(): T[] {
return this._screenStateService.entities;
}
get areAllEntitiesSelected() {
return this._screenStateService.areAllEntitiesSelected;
}
get areSomeEntitiesSelected$() {
return this._screenStateService.areSomeEntitiesSelected$;
} }
get sortingOption(): SortingOption { get sortingOption(): SortingOption {
return this._sortingService.getSortingOption(); return this._sortingService.getSortingOption();
} }
protected get _filters(): FilterWrapper[] { getFilter$(slug: string): Observable<FilterModel[]> {
return []; return this.filterService.getFilter$(slug);
} }
// ---- get searchForm() {
return this._searchService.searchForm;
private get _getSearchKey(): string {
if (!this._searchKey) throw new Error('Not implemented');
return this._searchKey;
}
// Search
private get _getSelectionKey(): string {
if (!this._selectionKey) throw new Error('Not implemented');
return this._selectionKey;
}
filtersChanged(filters?: { [key: string]: FilterModel[] } | FilterModel[]): void {
if (filters instanceof Array) this.showResetFilters = !!filters.find(f => f.checked);
else
for (const key of Object.keys(filters ?? {})) {
for (let idx = 0; idx < this[key]?.length; ++idx) {
this[key][idx] = filters[key][idx];
}
}
this._filterEntities();
} }
resetFilters() { resetFilters() {
this.showResetFilters = false; this.filterService.reset();
this.filtersChanged();
}
// Filter
toggleEntitySelected($event: MouseEvent, entity: T) {
$event.stopPropagation();
const idx = this.selectedEntitiesIds.indexOf(entity[this._getSelectionKey]);
if (idx === -1) {
this.selectedEntitiesIds.push(entity[this._getSelectionKey]);
} else {
this.selectedEntitiesIds.splice(idx, 1);
}
}
toggleSelectAll() {
if (this.areSomeEntitiesSelected) {
this.selectedEntitiesIds = [];
} else {
this.selectedEntitiesIds = this.displayedEntities.map(
entity => entity[this._getSelectionKey]
);
}
}
isSelected(entity: T) {
return this.selectedEntitiesIds.indexOf(entity[this._getSelectionKey]) !== -1;
} }
toggleSort($event) { toggleSort($event) {
this._sortingService.toggleSort($event); this._sortingService.toggleSort($event);
} }
// Selection toggleSelectAll() {
return this._screenStateService.toggleSelectAll();
protected _preFilter() {
return;
} }
protected _searchField(entity: T): string { toggleEntitySelected(event: MouseEvent, entity: T) {
return entity[this._getSearchKey]; event.stopPropagation();
return this._screenStateService.toggleEntitySelected(entity);
} }
@debounce(200) isSelected(entity: T) {
executeSearch(value: string) { return this._screenStateService.isSelected(entity);
this._searchValue = value;
this._executeSearchImmediately();
}
protected _executeSearchImmediately() {
this.displayedEntities = (
this._filters.length ? this.filteredEntities : this.allEntities
).filter(entity =>
this._searchField(entity).toLowerCase().includes(this._searchValue.toLowerCase())
);
this._updateSelection();
}
protected _updateSelection() {
if (this._selectionKey) {
this.selectedEntitiesIds = this.displayedEntities
.map(entity => entity[this._getSelectionKey])
.filter(id => this.selectedEntitiesIds.includes(id));
}
}
// Sort
protected _filterEntities() {
this._preFilter();
this.filteredEntities = getFilteredEntities(this.allEntities, this._filters);
this.executeSearch(this._searchValue);
this._changeDetectorRef.detectChanges();
} }
} }

View File

@ -1,83 +0,0 @@
import { Component, Injector, ViewChild } from '@angular/core';
import { SortingOption, SortingService } from '@services/sorting.service';
import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
import { FilterService } from '../services/filter.service';
import { SearchService } from '../services/search.service';
import { ScreenStateService } from '../services/screen-state.service';
import { Observable } from 'rxjs';
import { FilterModel } from '../components/filters/popup-filter/model/filter.model';
@Component({ template: '' })
export abstract class NewBaseListingComponent<T> {
@ViewChild(CdkVirtualScrollViewport)
readonly scrollViewport: CdkVirtualScrollViewport;
readonly filterService: FilterService<T>;
protected readonly _sortingService: SortingService;
protected readonly _searchService: SearchService<T>;
protected readonly _screenStateService: ScreenStateService<T>;
protected constructor(protected readonly _injector: Injector) {
this.filterService = this._injector.get<FilterService<T>>(FilterService);
this._sortingService = this._injector.get<SortingService>(SortingService);
this._searchService = this._injector.get<SearchService<T>>(SearchService);
this._screenStateService = this._injector.get<ScreenStateService<T>>(ScreenStateService);
}
get selectedEntitiesIds$(): Observable<string[]> {
return this._screenStateService.selectedEntitiesIds$;
}
get displayedEntities$(): Observable<T[]> {
return this._screenStateService.displayedEntities$;
}
get allEntities$(): Observable<T[]> {
return this._screenStateService.entities$;
}
get allEntities(): T[] {
return this._screenStateService.entities;
}
get areAllEntitiesSelected() {
return this._screenStateService.areAllEntitiesSelected;
}
get areSomeEntitiesSelected$() {
return this._screenStateService.areSomeEntitiesSelected$;
}
get sortingOption(): SortingOption {
return this._sortingService.getSortingOption();
}
getFilter$(slug: string): Observable<FilterModel[]> {
return this.filterService.getFilter$(slug);
}
get searchForm() {
return this._searchService.searchForm;
}
resetFilters() {
this.filterService.reset();
}
toggleSort($event) {
this._sortingService.toggleSort($event);
}
toggleSelectAll() {
return this._screenStateService.toggleSelectAll();
}
toggleEntitySelected(event: MouseEvent, entity: T) {
event.stopPropagation();
return this._screenStateService.toggleEntitySelected(entity);
}
isSelected(entity: T) {
return this._screenStateService.isSelected(entity);
}
}