trash screen fixes + update other listing screens

This commit is contained in:
Dan Percic 2021-07-14 13:53:16 +03:00
parent e1bfef2562
commit 421d9d734a
11 changed files with 97 additions and 76 deletions

View File

@ -23,18 +23,20 @@
<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">
{{ {{
'dossier-templates-listing.table-header.title' 'dossier-templates-listing.table-header.title'
| translate: { length: displayedEntities.length } | translate: { length: (displayedEntities$ | async).length }
}} }}
</span> </span>
<ng-container *ngIf="areSomeEntitiesSelected"> <ng-container *ngIf="areSomeEntitiesSelected$ | async">
<redaction-circle-button <redaction-circle-button
(action)="openDeleteTemplatesDialog($event)" (action)="openDeleteTemplatesDialog($event)"
*ngIf="permissionsService.isAdmin()" *ngIf="permissionsService.isAdmin()"
@ -65,7 +67,7 @@
</div> </div>
<div <div
[class.no-data]="!allEntities.length" [class.no-data]="(allEntities$ | async)?.length === 0"
class="table-header" class="table-header"
redactionSyncWidth="table-item" redactionSyncWidth="table-item"
> >
@ -100,13 +102,15 @@
</div> </div>
<redaction-empty-state <redaction-empty-state
*ngIf="!allEntities.length" *ngIf="(allEntities$ | async)?.length === 0"
icon="red:template" icon="red:template"
screen="dossier-templates-listing" screen="dossier-templates-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="dossier-templates-listing" screen="dossier-templates-listing"
type="no-match" type="no-match"
></redaction-empty-state> ></redaction-empty-state>
@ -114,7 +118,8 @@
<cdk-virtual-scroll-viewport [itemSize]="80" redactionHasScrollbar> <cdk-virtual-scroll-viewport [itemSize]="80" redactionHasScrollbar>
<div <div
*cdkVirtualFor=" *cdkVirtualFor="
let dossierTemplate of displayedEntities let dossierTemplate of displayedEntities$
| async
| sortBy: sortingOption.order:sortingOption.column | sortBy: sortingOption.order:sortingOption.column
" "
[routerLink]="[dossierTemplate.dossierTemplateId, 'dictionaries']" [routerLink]="[dossierTemplate.dossierTemplateId, 'dictionaries']"

View File

@ -1,9 +1,8 @@
import { Component, Injector, OnInit } from '@angular/core'; import { ChangeDetectionStrategy, Component, Injector, OnInit } from '@angular/core';
import { AppStateService } from '@state/app-state.service'; import { AppStateService } from '@state/app-state.service';
import { PermissionsService } from '@services/permissions.service'; import { PermissionsService } from '@services/permissions.service';
import { UserPreferenceService } from '@services/user-preference.service'; import { UserPreferenceService } from '@services/user-preference.service';
import { AdminDialogService } from '../../services/admin-dialog.service'; import { AdminDialogService } from '../../services/admin-dialog.service';
import { BaseListingComponent } from '@shared/base/base-listing.component';
import { DossierTemplateModelWrapper } from '../../../../models/file/dossier-template-model.wrapper'; import { DossierTemplateModelWrapper } from '../../../../models/file/dossier-template-model.wrapper';
import { LoadingService } from '../../../../services/loading.service'; import { LoadingService } from '../../../../services/loading.service';
import { DossierTemplateControllerService } from '@redaction/red-ui-http'; import { DossierTemplateControllerService } from '@redaction/red-ui-http';
@ -11,19 +10,18 @@ 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';
@Component({ @Component({
templateUrl: './dossier-templates-listing-screen.component.html', templateUrl: './dossier-templates-listing-screen.component.html',
styleUrls: ['./dossier-templates-listing-screen.component.scss'], styleUrls: ['./dossier-templates-listing-screen.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [FilterService, SearchService, ScreenStateService, SortingService] providers: [FilterService, SearchService, ScreenStateService, SortingService]
}) })
export class DossierTemplatesListingScreenComponent export class DossierTemplatesListingScreenComponent
extends BaseListingComponent<DossierTemplateModelWrapper> extends NewBaseListingComponent<DossierTemplateModelWrapper>
implements OnInit implements OnInit
{ {
protected readonly _searchKey = 'name';
protected readonly _selectionKey = 'dossierTemplateId';
constructor( constructor(
private readonly _dialogService: AdminDialogService, private readonly _dialogService: AdminDialogService,
private readonly _appStateService: AppStateService, private readonly _appStateService: AppStateService,
@ -35,6 +33,8 @@ export class DossierTemplatesListingScreenComponent
) { ) {
super(_injector); super(_injector);
this._sortingService.setScreenName(ScreenNames.DOSSIER_TEMPLATES_LISTING); this._sortingService.setScreenName(ScreenNames.DOSSIER_TEMPLATES_LISTING);
this._searchService.setSearchKey('name');
this._screenStateService.setIdKey('dossierTemplateId');
} }
ngOnInit(): void { ngOnInit(): void {
@ -45,9 +45,9 @@ export class DossierTemplatesListingScreenComponent
return this._dialogService.openDialog('confirm', $event, null, async () => { return this._dialogService.openDialog('confirm', $event, null, async () => {
this._loadingService.start(); this._loadingService.start();
await this._dossierTemplateControllerService await this._dossierTemplateControllerService
.deleteDossierTemplates(this.selectedEntitiesIds) .deleteDossierTemplates(this._screenStateService.selectedEntitiesIds)
.toPromise(); .toPromise();
this.selectedEntitiesIds = []; this._screenStateService.setSelectedEntitiesIds([]);
await this._appStateService.loadAllDossierTemplates(); await this._appStateService.loadAllDossierTemplates();
await this._appStateService.loadDictionaryData(); await this._appStateService.loadDictionaryData();
this.loadDossierTemplatesData(); this.loadDossierTemplatesData();
@ -57,8 +57,8 @@ export class DossierTemplatesListingScreenComponent
loadDossierTemplatesData() { loadDossierTemplatesData() {
this._loadingService.start(); this._loadingService.start();
this._appStateService.reset(); this._appStateService.reset();
this.allEntities = this._appStateService.dossierTemplates; this._screenStateService.setEntities(this._appStateService.dossierTemplates);
this._executeSearchImmediately(); this.filterService.filterEntities();
this._loadDossierTemplateStats(); this._loadDossierTemplateStats();
this._loadingService.stop(); this._loadingService.stop();
} }
@ -77,7 +77,7 @@ export class DossierTemplatesListingScreenComponent
} }
private _loadDossierTemplateStats() { private _loadDossierTemplateStats() {
this.allEntities.forEach(rs => { this._screenStateService.entities.forEach(rs => {
const dictionaries = this._appStateService.dictionaryData[rs.dossierTemplateId]; const dictionaries = this._appStateService.dictionaryData[rs.dossierTemplateId];
if (dictionaries) { if (dictionaries) {
rs.dictionariesCount = Object.keys(dictionaries) rs.dictionariesCount = Object.keys(dictionaries)

View File

@ -13,7 +13,9 @@
<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>
@ -24,9 +26,17 @@
}} }}
</span> </span>
<redaction-circle-button
(action)="bulkRestore()"
*ngIf="areSomeEntitiesSelected$ | async"
icon="red:undo"
[tooltip]="'trash.bulk.restore' | translate"
type="dark-bg"
></redaction-circle-button>
<redaction-circle-button <redaction-circle-button
(action)="bulkDelete()" (action)="bulkDelete()"
*ngIf="areSomeEntitiesSelected" *ngIf="areSomeEntitiesSelected$ | async"
icon="red:trash" icon="red:trash"
[tooltip]="'trash.bulk.delete' | translate" [tooltip]="'trash.bulk.delete' | translate"
type="dark-bg" type="dark-bg"
@ -143,14 +153,14 @@
</div> </div>
<div class="action-buttons"> <div class="action-buttons">
<redaction-circle-button <redaction-circle-button
(action)="restore(entity.dossierId)" (action)="bulkRestore([entity.dossierId])"
icon="red:undo" icon="red:undo"
[tooltip]="'trash.action.restore' | translate" [tooltip]="'trash.action.restore' | translate"
type="dark-bg" type="dark-bg"
></redaction-circle-button> ></redaction-circle-button>
<redaction-circle-button <redaction-circle-button
(action)="hardDelete(entity.dossierId)" (action)="bulkDelete([entity.dossierId])"
icon="red:trash" icon="red:trash"
[tooltip]="'trash.action.delete' | translate" [tooltip]="'trash.action.delete' | translate"
type="dark-bg" type="dark-bg"

View File

@ -58,40 +58,32 @@ export class TrashScreenComponent extends NewBaseListingComponent<Dossier> imple
return moment(softDeletedTime).add(this._deleteRetentionHours, 'hours').toISOString(); return moment(softDeletedTime).add(this._deleteRetentionHours, 'hours').toISOString();
} }
restore(dossierId: string): void {
this._loadingService.loadWhile(this._restore(dossierId));
}
hardDelete(dossierId: string): void {
this._loadingService.loadWhile(this._hardDelete(dossierId));
}
async bulkDelete() {
this._loadingService.start();
const dossierIds = this._screenStateService.selectedEntitiesIds;
for (const dossierId of dossierIds) await this._hardDelete(dossierId);
this._loadingService.stop();
}
trackById(index: number, dossier: Dossier): string { trackById(index: number, dossier: Dossier): string {
return dossier.dossierId; return dossier.dossierId;
} }
private async _restore(dossierId: string): Promise<void> { bulkDelete(dossierIds = this._screenStateService.selectedEntitiesIds) {
await this._dossiersService.restore(dossierId); this._loadingService.loadWhile(this._hardDelete(dossierIds));
this._removeFromList(dossierId);
} }
private async _hardDelete(dossierId: string): Promise<void> { bulkRestore(dossierIds = this._screenStateService.selectedEntitiesIds) {
await this._dossiersService.hardDelete(dossierId); this._loadingService.loadWhile(this._restore(dossierIds));
this._removeFromList(dossierId);
} }
private _removeFromList(dossierId: string): void { private async _restore(dossierIds: string[]): Promise<void> {
const entities = this._screenStateService.entities.filter(e => e.dossierId !== dossierId); await this._dossiersService.restore(dossierIds);
this._removeFromList(dossierIds);
}
private async _hardDelete(dossierIds: string[]): Promise<void> {
await this._dossiersService.hardDelete(dossierIds);
this._removeFromList(dossierIds);
}
private _removeFromList(ids: string[]): void {
const entities = this._screenStateService.entities.filter(e => !ids.includes(e.dossierId));
this._screenStateService.setEntities(entities); this._screenStateService.setEntities(entities);
this._screenStateService.setSelectedEntitiesIds([]);
this.filterService.filterEntities(); this.filterService.filterEntities();
} }
} }

View File

@ -38,20 +38,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">
{{ {{
'user-listing.table-header.title' 'user-listing.table-header.title'
| translate: { length: displayedEntities.length } | translate: { length: (displayedEntities$ | async)?.length }
}} }}
</span> </span>
<redaction-circle-button <redaction-circle-button
(action)="bulkDelete()" (action)="bulkDelete()"
*ngIf="areSomeEntitiesSelected" *ngIf="areSomeEntitiesSelected$ | async"
[disabled]="!canDeleteSelected" [disabled]="!canDeleteSelected"
[tooltip]=" [tooltip]="
canDeleteSelected canDeleteSelected
@ -89,7 +91,7 @@
</div> </div>
<redaction-empty-state <redaction-empty-state
*ngIf="!displayedEntities.length" *ngIf="(displayedEntities$ | async)?.length === 0"
screen="user-listing" screen="user-listing"
type="no-match" type="no-match"
></redaction-empty-state> ></redaction-empty-state>
@ -97,7 +99,7 @@
<cdk-virtual-scroll-viewport [itemSize]="80" redactionHasScrollbar> <cdk-virtual-scroll-viewport [itemSize]="80" redactionHasScrollbar>
<!-- Table lines --> <!-- Table lines -->
<div <div
*cdkVirtualFor="let user of displayedEntities; trackBy: trackById" *cdkVirtualFor="let user of displayedEntities$ | async; trackBy: trackById"
class="table-item" class="table-item"
> >
<div (click)="toggleEntitySelected($event, user)" class="selection-column"> <div (click)="toggleEntitySelected($event, user)" class="selection-column">

View File

@ -6,23 +6,22 @@ import { AdminDialogService } from '../../services/admin-dialog.service';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { DoughnutChartConfig } from '@shared/components/simple-doughnut-chart/simple-doughnut-chart.component'; import { DoughnutChartConfig } from '@shared/components/simple-doughnut-chart/simple-doughnut-chart.component';
import { TranslateChartService } from '@services/translate-chart.service'; import { TranslateChartService } from '@services/translate-chart.service';
import { BaseListingComponent } from '@shared/base/base-listing.component';
import { LoadingService } from '../../../../services/loading.service'; import { LoadingService } from '../../../../services/loading.service';
import { InitialsAvatarComponent } from '../../../shared/components/initials-avatar/initials-avatar.component'; import { InitialsAvatarComponent } from '../../../shared/components/initials-avatar/initials-avatar.component';
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 { NewBaseListingComponent } from '../../../shared/base/new-base-listing.component';
@Component({ @Component({
templateUrl: './user-listing-screen.component.html', templateUrl: './user-listing-screen.component.html',
styleUrls: ['./user-listing-screen.component.scss'], styleUrls: ['./user-listing-screen.component.scss'],
providers: [FilterService, SearchService, ScreenStateService, SortingService] providers: [FilterService, SearchService, ScreenStateService, SortingService]
}) })
export class UserListingScreenComponent extends BaseListingComponent<User> implements OnInit { export class UserListingScreenComponent extends NewBaseListingComponent<User> implements OnInit {
collapsedDetails = false; collapsedDetails = false;
chartData: DoughnutChartConfig[] = []; chartData: DoughnutChartConfig[] = [];
protected readonly _selectionKey = 'userId';
@ViewChildren(InitialsAvatarComponent) @ViewChildren(InitialsAvatarComponent)
private readonly _avatars: QueryList<InitialsAvatarComponent>; private readonly _avatars: QueryList<InitialsAvatarComponent>;
@ -37,10 +36,11 @@ export class UserListingScreenComponent extends BaseListingComponent<User> imple
protected readonly _injector: Injector protected readonly _injector: Injector
) { ) {
super(_injector); super(_injector);
this._screenStateService.setIdKey('userId');
} }
get canDeleteSelected(): boolean { get canDeleteSelected(): boolean {
return this.selectedEntitiesIds.indexOf(this.userService.userId) === -1; return this._screenStateService.selectedEntitiesIds.indexOf(this.userService.userId) === -1;
} }
async ngOnInit() { async ngOnInit() {
@ -79,7 +79,9 @@ export class UserListingScreenComponent extends BaseListingComponent<User> imple
} }
async bulkDelete() { async bulkDelete() {
this.openDeleteUsersDialog(this.allEntities.filter(u => this.isSelected(u))); this.openDeleteUsersDialog(
this._screenStateService.entities.filter(u => this.isSelected(u))
);
} }
trackById(index: number, user: User) { trackById(index: number, user: User) {
@ -91,9 +93,11 @@ export class UserListingScreenComponent extends BaseListingComponent<User> imple
} }
private async _loadData() { private async _loadData() {
this.allEntities = await this._userControllerService.getAllUsers().toPromise(); this._screenStateService.setEntities(
await this._userControllerService.getAllUsers().toPromise()
);
await this.userService.loadAllUsers(); await this.userService.loadAllUsers();
this._executeSearchImmediately(); this.filterService.filterEntities();
this._computeStats(); this._computeStats();
this._loadingService.stop(); this._loadingService.stop();
} }

View File

@ -5,7 +5,7 @@
[searchPlaceholder]="'dossier-overview.search' | translate" [searchPlaceholder]="'dossier-overview.search' | translate"
> >
<redaction-file-download-btn <redaction-file-download-btn
[disabled]="areSomeEntitiesSelected" [disabled]="areSomeEntitiesSelected$ | async"
[dossier]="activeDossier" [dossier]="activeDossier"
[file]="allEntities$ | async" [file]="allEntities$ | async"
tooltipPosition="below" tooltipPosition="below"
@ -14,8 +14,8 @@
<redaction-circle-button <redaction-circle-button
(action)="reanalyseDossier()" (action)="reanalyseDossier()"
*ngIf="permissionsService.displayReanalyseBtn()" *ngIf="permissionsService.displayReanalyseBtn()"
[disabled]="areSomeEntitiesSelected" [disabled]="areSomeEntitiesSelected$ | async"
[tooltipClass]="'small ' + (areSomeEntitiesSelected ? '' : 'warn')" [tooltipClass]="'small ' + ((areSomeEntitiesSelected$ | async) ? '' : 'warn')"
[tooltip]="'dossier-overview.new-rule.toast.actions.reanalyse-all' | translate" [tooltip]="'dossier-overview.new-rule.toast.actions.reanalyse-all' | translate"
icon="red:refresh" icon="red:refresh"
tooltipPosition="below" tooltipPosition="below"
@ -41,7 +41,9 @@
<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>

View File

@ -9,13 +9,11 @@ export class DossiersService {
return this._dossierControllerService.getDeletedDossiers().toPromise(); return this._dossierControllerService.getDeletedDossiers().toPromise();
} }
restore(dossierIds: string | Array<string>): Promise<unknown> { restore(dossierIds: Array<string>): Promise<unknown> {
if (typeof dossierIds === 'string') dossierIds = [dossierIds];
return this._dossierControllerService.restoreDossiers(dossierIds).toPromise(); return this._dossierControllerService.restoreDossiers(dossierIds).toPromise();
} }
hardDelete(dossierIds: string | Array<string>): Promise<unknown> { hardDelete(dossierIds: Array<string>): Promise<unknown> {
if (typeof dossierIds === 'string') dossierIds = [dossierIds];
return this._dossierControllerService.hardDeleteDossiers(dossierIds).toPromise(); return this._dossierControllerService.hardDeleteDossiers(dossierIds).toPromise();
} }
} }

View File

@ -37,12 +37,16 @@ export abstract class NewBaseListingComponent<T> {
return this._screenStateService.entities$; return this._screenStateService.entities$;
} }
get allEntities(): T[] {
return this._screenStateService.entities;
}
get areAllEntitiesSelected() { get areAllEntitiesSelected() {
return this._screenStateService.areAllEntitiesSelected; return this._screenStateService.areAllEntitiesSelected;
} }
get areSomeEntitiesSelected() { get areSomeEntitiesSelected$() {
return this._screenStateService.areSomeEntitiesSelected; return this._screenStateService.areSomeEntitiesSelected$;
} }
get sortingOption(): SortingOption { get sortingOption(): SortingOption {
@ -53,8 +57,8 @@ export abstract class NewBaseListingComponent<T> {
return this.filterService.getFilter$(slug); return this.filterService.getFilter$(slug);
} }
protected get _filters(): FilterWrapper[] { get searchForm() {
return []; return this._searchService.searchForm;
} }
resetFilters() { resetFilters() {

View File

@ -68,11 +68,14 @@ export class FilterService<T> {
} }
getFilter$(slug: string): Observable<FilterModel[]> { getFilter$(slug: string): Observable<FilterModel[]> {
return this.getFilterWrapper$(slug).pipe(map(f => f.values)); return this.getFilterWrapper$(slug).pipe(
filter(f => f !== null && f !== undefined),
map(f => f?.values)
);
} }
getFilterWrapper$(slug: string): Observable<FilterWrapper> { getFilterWrapper$(slug: string): Observable<FilterWrapper> {
return this.allFilters$.pipe(map(all => all.find(f => f.slug === slug))); return this.allFilters$.pipe(map(all => all.find(f => f?.slug === slug)));
} }
get allFilters$(): Observable<FilterWrapper[]> { get allFilters$(): Observable<FilterWrapper[]> {
@ -90,6 +93,7 @@ export class FilterService<T> {
}); });
}); });
}); });
this._allFilters$.next(this.filters);
this.filterEntities(); this.filterEntities();
} }

View File

@ -61,8 +61,8 @@ export class ScreenStateService<T> {
); );
} }
get areSomeEntitiesSelected(): boolean { get areSomeEntitiesSelected$(): Observable<boolean> {
return this.selectedEntitiesIds.length > 0; return this.selectedEntitiesIds$.pipe(map(all => all.length > 0));
} }
isSelected(entity: T): boolean { isSelected(entity: T): boolean {
@ -81,7 +81,7 @@ export class ScreenStateService<T> {
} }
toggleSelectAll(): void { toggleSelectAll(): void {
if (this.areSomeEntitiesSelected) return this.selectedEntitiesIds$.next([]); if (this.areSomeEntitiesSelected$) return this.selectedEntitiesIds$.next([]);
this.setSelectedEntitiesIds(this._displayedEntitiesIds); this.setSelectedEntitiesIds(this._displayedEntitiesIds);
} }