Trash screen & some other improvements

This commit is contained in:
Adina Țeudan 2021-08-09 16:06:26 +03:00
parent 2e4a2c1686
commit bfd45a8894
9 changed files with 150 additions and 118 deletions

View File

@ -5,6 +5,7 @@ import { DownloadControllerService } from '@redaction/red-ui-http';
import { BaseListingComponent, DefaultListingServices } from '@shared/base/base-listing.component';
import { CircleButtonTypes, TableColumnConfig } from '@iqser/common-ui';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { LoadingService } from '@services/loading.service';
@Component({
selector: 'redaction-downloads-list-screen',
@ -27,22 +28,28 @@ export class DownloadsListScreenComponent extends BaseListingComponent<DownloadS
constructor(
readonly fileDownloadService: FileDownloadService,
private readonly _downloadControllerService: DownloadControllerService,
private readonly _loadingService: LoadingService,
protected readonly _injector: Injector
) {
super(_injector);
}
async ngOnInit() {
await this._loadData();
this._loadingService.loadWhile(this._loadData());
}
async downloadItem(download: DownloadStatusWrapper) {
await this.fileDownloadService.performDownload(download);
downloadItem(download: DownloadStatusWrapper) {
this._loadingService.loadWhile(this.fileDownloadService.performDownload(download));
}
async deleteItems(downloads?: DownloadStatusWrapper[]) {
const storageIds = (downloads || this.entitiesService.selected).map(d => d.storageId);
deleteItems(downloads?: DownloadStatusWrapper[]) {
this._loadingService.loadWhile(this._deleteItems(downloads));
}
private async _deleteItems(downloads?: DownloadStatusWrapper[]) {
const storageIds = (downloads || this.screenStateService.selectedEntities).map(d => d.storageId);
await this._downloadControllerService.deleteDownload({ storageIds }).toPromise();
this.screenStateService.setSelectedEntities([]);
await this._loadData();
}

View File

@ -5,55 +5,12 @@
<div class="red-content-inner">
<div class="content-container">
<div class="header-item">
<iqser-round-checkbox
(click)="toggleSelectAll()"
[active]="entitiesService.areAllSelected$ | async"
[indeterminate]="entitiesService.notAllSelected$ | async"
></iqser-round-checkbox>
<span class="all-caps-label">
{{ 'trash.table-header.title' | translate: { length: (entitiesService.displayedLength$ | async) } }}
</span>
<iqser-circle-button
(action)="bulkRestore()"
*ngIf="entitiesService.areSomeSelected$ | async"
[tooltip]="'trash.bulk.restore' | translate"
icon="red:put-back"
[type]="circleButtonTypes.dark"
></iqser-circle-button>
<iqser-circle-button
(action)="bulkDelete()"
*ngIf="entitiesService.areSomeSelected$ | async"
[tooltip]="'trash.bulk.delete' | translate"
icon="red:trash"
[type]="circleButtonTypes.dark"
></iqser-circle-button>
</div>
<div [class.no-data]="entitiesService.noData$ | async" class="table-header" redactionSyncWidth="table-item">
<div class="select-oval-placeholder"></div>
<iqser-table-column-name
[label]="'trash.table-col-names.name' | translate"
[withSort]="true"
column="dossierName"
></iqser-table-column-name>
<iqser-table-column-name [label]="'trash.table-col-names.owner' | translate" class="user-column"></iqser-table-column-name>
<iqser-table-column-name
[label]="'trash.table-col-names.deleted-on' | translate"
[withSort]="true"
column="softDeletedTime"
></iqser-table-column-name>
<iqser-table-column-name
[label]="'trash.table-col-names.time-to-restore' | translate"
[withSort]="true"
column="softDeletedTime"
></iqser-table-column-name>
<div class="scrollbar-placeholder"></div>
</div>
<redaction-table-header
[bulkActions]="bulkActions"
[selectionEnabled]="true"
[tableColConfigs]="tableColConfigs"
[tableHeaderLabel]="tableHeaderLabel"
></redaction-table-header>
<redaction-empty-state
*ngIf="entitiesService.noData$ | async"
@ -103,18 +60,18 @@
</div>
<div class="action-buttons">
<iqser-circle-button
*ngIf="canRestore(entity.softDeletedTime)"
(action)="bulkRestore([entity.dossierId])"
(action)="restore([entity])"
*ngIf="entity.canRestore"
[tooltip]="'trash.action.restore' | translate"
icon="red:put-back"
[type]="circleButtonTypes.dark"
icon="red:put-back"
></iqser-circle-button>
<iqser-circle-button
(action)="bulkDelete([entity])"
(action)="hardDelete([entity])"
[tooltip]="'trash.action.delete' | translate"
icon="red:trash"
[type]="circleButtonTypes.dark"
icon="red:trash"
></iqser-circle-button>
</div>
</div>
@ -125,3 +82,23 @@
</div>
</div>
</section>
<ng-template #bulkActions>
<div class="bulk-actions">
<iqser-circle-button
(action)="restore()"
*ngIf="canRestoreSelected$ | async"
[tooltip]="'trash.bulk.restore' | translate"
[type]="circleButtonTypes.dark"
icon="red:put-back"
></iqser-circle-button>
<iqser-circle-button
(action)="hardDelete()"
*ngIf="screenStateService.areSomeEntitiesSelected$ | async"
[tooltip]="'trash.bulk.delete' | translate"
[type]="circleButtonTypes.dark"
icon="red:trash"
></iqser-circle-button>
</div>
</ng-template>

View File

@ -1,11 +1,12 @@
@import '../../../../../assets/styles/variables';
@import '../../../../../assets/styles/red-mixins';
.header-item {
padding: 0 24px 0 10px;
.bulk-actions {
display: flex;
align-items: center;
iqser-circle-button:not(:last-child) {
margin-right: 4px !important;
> *:not(:last-child) {
margin-right: 2px;
}
}

View File

@ -10,20 +10,49 @@ import { DossiersService } from '../../../dossier/services/dossiers.service';
import { AdminDialogService } from '../../services/admin-dialog.service';
import { ConfirmationDialogInput, TitleColors } from '@shared/dialogs/confirmation-dialog/confirmation-dialog.component';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { Observable } from 'rxjs';
import { distinctUntilChanged, map } from 'rxjs/operators';
const HOURS_IN_A_DAY = 24;
const MINUTES_IN_AN_HOUR = 60;
interface DossierListItem extends Dossier {
readonly canRestore: boolean;
}
@Component({
templateUrl: './trash-screen.component.html',
styleUrls: ['./trash-screen.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [...DefaultListingServices, DossiersService]
})
export class TrashScreenComponent extends BaseListingComponent<Dossier> implements OnInit {
readonly itemSize = 80;
protected readonly _primaryKey = 'dossierName';
export class TrashScreenComponent extends BaseListingComponent<DossierListItem> implements OnInit {
readonly circleButtonTypes = CircleButtonTypes;
readonly itemSize = 80;
readonly tableColConfigs: TableColConfig[] = [
{
label: _('trash.table-col-names.name'),
withSort: true,
column: 'dossierName'
},
{
label: _('trash.table-col-names.owner'),
class: 'user-column'
},
{
label: _('trash.table-col-names.deleted-on'),
withSort: true,
column: 'softDeletedTime'
},
{
label: _('trash.table-col-names.time-to-restore'),
withSort: true,
column: 'softDeletedTime'
}
];
readonly canRestoreSelected$ = this._canRestoreSelected$;
protected readonly _primaryKey = 'dossierName';
protected readonly _tableHeaderLabel = 'trash.table-header.title';
private readonly _deleteRetentionHours = this._appConfigService.getConfig(AppConfigKey.DELETE_RETENTION_HOURS);
constructor(
@ -37,51 +66,22 @@ export class TrashScreenComponent extends BaseListingComponent<Dossier> implemen
super(_injector);
}
private get _canRestoreSelected$(): Observable<boolean> {
return this.screenStateService.selectedEntities$.pipe(
map(entities => entities.length && this._canRestore()),
distinctUntilChanged()
);
}
async ngOnInit(): Promise<void> {
this._loadingService.start();
await this.loadDossierTemplatesData();
this._loadingService.stop();
}
async loadDossierTemplatesData(): Promise<void> {
this.entitiesService.setEntities(await this._dossiersService.getDeleted());
}
canRestore(softDeletedTime: string): boolean {
const date = moment(this.getRestoreDate(softDeletedTime));
const now = new Date(Date.now());
const daysLeft = date.diff(now, 'days');
const hoursFromNow = date.diff(now, 'hours');
const hoursLeft = hoursFromNow - HOURS_IN_A_DAY * daysLeft;
const minutesFromNow = date.diff(now, 'minutes');
const minutesLeft = minutesFromNow - HOURS_IN_A_DAY * MINUTES_IN_AN_HOUR * daysLeft;
return daysLeft >= 0 && hoursLeft >= 0 && minutesLeft > 0;
this._loadingService.loadWhile(this._loadDossiersData());
}
getRestoreDate(softDeletedTime: string): string {
return moment(softDeletedTime).add(this._deleteRetentionHours, 'hours').toISOString();
}
bulkDelete(dossiers = this.entitiesService.selected) {
this._loadingService.loadWhile(this._hardDelete(dossiers));
}
bulkRestore(dossierIds = this.entitiesService.selected.map(d => d.dossierId)) {
this._loadingService.loadWhile(this._restore(dossierIds));
}
private async _restore(dossierIds: string[]): Promise<void> {
this._loadingService.start();
await this._dossiersService.restore(dossierIds);
this._removeFromList(dossierIds);
this._loadingService.stop();
}
private async _hardDelete(dossiers: Dossier[]): Promise<void> {
hardDelete(dossiers = this.screenStateService.selectedEntities) {
const period = this._appConfigService.getConfig('DELETE_RETENTION_HOURS');
const data = new ConfirmationDialogInput({
title: dossiers.length > 1 ? _('confirmation-dialog.delete-dossier.title-alt') : _('confirmation-dialog.delete-dossier.title'),
@ -94,14 +94,58 @@ export class TrashScreenComponent extends BaseListingComponent<Dossier> implemen
translateParams: { dossierName: dossiers[0].dossierName, period: period }
});
this._adminDialogService.openDialog('confirm', null, data, async () => {
this._loadingService.start();
const dossierIds = dossiers.map(d => d.dossierId);
await this._dossiersService.hardDelete(dossierIds);
this._removeFromList(dossierIds);
this._loadingService.stop();
this._loadingService.loadWhile(this._hardDelete(dossiers));
});
}
restore(dossiers = this.screenStateService.selectedEntities) {
this._loadingService.loadWhile(this._restore(dossiers));
}
private async _loadDossiersData(): Promise<void> {
this.screenStateService.setEntities(this._toListItems(await this._dossiersService.getDeleted()));
}
private _canRestore(dossiers = this.screenStateService.selectedEntities): boolean {
return dossiers.reduce((prev, dossier) => prev && dossier.canRestore, true);
}
private _canRestoreDossier(dossier: Dossier): boolean {
const date = moment(this.getRestoreDate(dossier.softDeletedTime));
const now = new Date(Date.now());
const daysLeft = date.diff(now, 'days');
const hoursFromNow = date.diff(now, 'hours');
const hoursLeft = hoursFromNow - HOURS_IN_A_DAY * daysLeft;
const minutesFromNow = date.diff(now, 'minutes');
const minutesLeft = minutesFromNow - HOURS_IN_A_DAY * MINUTES_IN_AN_HOUR * daysLeft;
return daysLeft >= 0 && hoursLeft >= 0 && minutesLeft > 0;
}
private _toListItems(dossiers: Dossier[]): DossierListItem[] {
return dossiers.map(dossier => this._toListItem(dossier));
}
private _toListItem(dossier: Dossier): DossierListItem {
return {
...dossier,
canRestore: this._canRestoreDossier(dossier)
};
}
private async _restore(dossiers: DossierListItem[]): Promise<void> {
const dossierIds = dossiers.map(d => d.dossierId);
await this._dossiersService.restore(dossierIds);
this._removeFromList(dossierIds);
}
private async _hardDelete(dossiers: DossierListItem[]) {
const dossierIds = dossiers.map(d => d.dossierId);
await this._dossiersService.hardDelete(dossierIds);
this._removeFromList(dossierIds);
}
private _removeFromList(ids: string[]): void {
const entities = this.entitiesService.all.filter(e => !ids.includes(e.dossierId));
this.entitiesService.setEntities(entities);

View File

@ -41,7 +41,7 @@ export class DossierListingScreenComponent
extends BaseListingComponent<DossierWrapper>
implements OnInit, AfterViewInit, OnDestroy, OnAttach, OnDetach
{
readonly itemSize = 95;
readonly itemSize = 85;
protected readonly _primaryKey = 'dossierName';
readonly currentUser = this._userService.currentUser;
readonly tableHeaderLabel = _('dossier-listing.table-header.title');

View File

@ -15,7 +15,12 @@
<redaction-quick-filters></redaction-quick-filters>
</div>
<div [class.selection-enabled]="selectionEnabled" class="table-header" redactionSyncWidth="table-item">
<div
[class.no-data]="screenStateService.noData$ | async"
[class.selection-enabled]="selectionEnabled"
class="table-header"
redactionSyncWidth="table-item"
>
<div *ngIf="selectionEnabled" class="select-oval-placeholder"></div>
<iqser-table-column-name

View File

@ -1,6 +1,6 @@
{
"OAUTH_URL": "https://red-staging.iqser.cloud/auth/realms/redaction",
"API_URL": "https://red-staging.iqser.cloud/redaction-gateway-v1",
"OAUTH_URL": "https://dev-06.iqser.cloud/auth/realms/redaction",
"API_URL": "https://dev-06.iqser.cloud/redaction-gateway-v1",
"OAUTH_CLIENT_ID": "redaction",
"BACKEND_APP_VERSION": "4.4.40",
"FRONTEND_APP_VERSION": "1.1",

View File

@ -1,11 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="14px" height="14px" viewBox="0 0 14 14" version="1.1"
<svg height="14px" version="1.1" viewBox="0 0 14 14" width="14px"
xmlns="http://www.w3.org/2000/svg">
<title>B99F3D5E-879A-47E3-A8C7-84877EE418F3</title>
<g id="04.Search-all-in-Document" transform="translate(-319.000000, -144.000000)">
<g id="Group-30" transform="translate(295.000000, 121.000000)">
<g id="collapse" transform="translate(24.000000, 23.000000)" fill="currentColor"
fill-rule="nonzero">
<g fill="currentColor" fill-rule="nonzero" id="collapse"
transform="translate(24.000000, 23.000000)">
<path
d="M2.38,0 L2.38,7.98 L10.5,7.98 L8.75,6.16 L9.73,5.18 L13.16,8.68 L9.73,12.18 L8.75,11.2 L10.5,9.38 L1.96,9.38 L1.959,9.379 L0.98,9.38 L0.98,0 L2.38,0 Z"
id="Combined-Shape"></path>

Before

Width:  |  Height:  |  Size: 781 B

After

Width:  |  Height:  |  Size: 725 B

View File

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg height="14px" version="1.1" viewBox="0 0 14 14" width="14px"
xmlns="http://www.w3.org/2000/svg">
<title>711C9D82-CAA8-47BE-954A-A9DA22CE85E6</title>
<g fill="currentColor" fill-rule="evenodd" id="Trash" stroke="none" stroke-width="1">
<g id="05.-Trash-bulk-actions" transform="translate(-133.000000, -130.000000)">
<g id="Group-36" transform="translate(0.000000, 112.000000)">

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB