Pull request #262: Trash screen & dossier templates listing improvements

Merge in RED/ui from trash-screen to master

* commit 'dcf62b512e5e566ea49a4c3580ac03a906c995d1':
  fix rebase
  Fixes
  Dossier templates listing header
  Trash screen & some other improvements
This commit is contained in:
Adina Teudan 2021-08-09 17:24:14 +02:00
commit 4741eebbc6
15 changed files with 232 additions and 208 deletions

View File

@ -7,7 +7,7 @@
[bulkActions]="bulkActions"
[hasEmptyColumn]="true"
[selectionEnabled]="true"
[tableColumnConfigs]="tableColConfigs"
[tableColumnConfigs]="tableColumnConfigs"
[tableHeaderLabel]="tableHeaderLabel"
></redaction-table-header>

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',
@ -13,11 +14,11 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
providers: [...DefaultListingServices]
})
export class DownloadsListScreenComponent extends BaseListingComponent<DownloadStatusWrapper> implements OnInit {
readonly circleButtonTypes = CircleButtonTypes;
readonly itemSize = 80;
protected readonly _primaryKey = 'storageId';
readonly circleButtonTypes = CircleButtonTypes;
readonly tableHeaderLabel = _('downloads-list.table-header.title');
readonly tableColConfigs: TableColumnConfig<DownloadStatusWrapper>[] = [
readonly tableColumnConfigs: TableColumnConfig<DownloadStatusWrapper>[] = [
{ label: _('downloads-list.table-col-names.name') },
{ label: _('downloads-list.table-col-names.size') },
{ label: _('downloads-list.table-col-names.date') },
@ -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[]) {
deleteItems(downloads?: DownloadStatusWrapper[]) {
this._loadingService.loadWhile(this._deleteItems(downloads));
}
private async _deleteItems(downloads?: DownloadStatusWrapper[]) {
const storageIds = (downloads || this.entitiesService.selected).map(d => d.storageId);
await this._downloadControllerService.deleteDownload({ storageIds }).toPromise();
this.entitiesService.setSelected([]);
await this._loadData();
}

View File

@ -33,8 +33,6 @@ export class AddEditDossierTemplateDialogComponent implements OnInit {
private _previousValidFrom: Moment;
private _previousValidTo: Moment;
reportTemplateValueMapper = (reportTemplate: ReportTemplate) => reportTemplate.templateId;
constructor(
private readonly _appStateService: AppStateService,
private readonly _formBuilder: FormBuilder,
@ -68,11 +66,6 @@ export class AddEditDossierTemplateDialogComponent implements OnInit {
});
}
async ngOnInit() {
this.availableReportTypes =
(await this._reportTemplateController.getAvailableReportTemplates(this.dossierTemplate?.dossierTemplateId).toPromise()) || [];
}
get changed(): boolean {
if (!this.dossierTemplate) return true;
@ -105,6 +98,13 @@ export class AddEditDossierTemplateDialogComponent implements OnInit {
return false;
}
reportTemplateValueMapper = (reportTemplate: ReportTemplate) => reportTemplate.templateId;
async ngOnInit() {
this.availableReportTypes =
(await this._reportTemplateController.getAvailableReportTemplates(this.dossierTemplate?.dossierTemplateId).toPromise()) || [];
}
async saveDossierTemplate() {
const dossierTemplate = {
dossierTemplateId: this.dossierTemplate?.dossierTemplateId,
@ -115,7 +115,7 @@ export class AddEditDossierTemplateDialogComponent implements OnInit {
await this._dossierTemplateController.createOrUpdateDossierTemplate(dossierTemplate).toPromise();
await this._appStateService.loadAllDossierTemplates();
await this._appStateService.loadDictionaryData();
this.dialogRef.close(dossierTemplate);
this.dialogRef.close(true);
}
private _applyValidityIntervalConstraints(value): boolean {

View File

@ -8,28 +8,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">
{{
'dossier-templates-listing.table-header.title'
| translate: { length: (entitiesService.displayedLength$ | async) }
}}
</span>
<iqser-circle-button
(action)="openDeleteTemplatesDialog($event)"
*ngIf="canBulkDelete$(currentUser.isAdmin) | async"
[tooltip]="'dossier-templates-listing.bulk.delete' | translate"
icon="red:trash"
[type]="circleButtonTypes.dark"
></iqser-circle-button>
<redaction-table-header
[bulkActions]="bulkActions"
[selectionEnabled]="true"
[tableColumnConfigs]="tableColumnConfigs"
[tableHeaderLabel]="tableHeaderLabel"
>
<div class="actions flex-1">
<redaction-input-with-action
[form]="searchService.searchForm"
@ -41,37 +25,11 @@
(action)="openAddDossierTemplateDialog()"
*ngIf="currentUser.isAdmin && userPreferenceService.areDevFeaturesEnabled"
[label]="'dossier-templates-listing.add-new' | translate"
icon="red:plus"
[type]="iconButtonTypes.primary"
icon="red:plus"
></iqser-icon-button>
</div>
</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]="'dossier-templates-listing.table-col-names.name' | translate"
[withSort]="true"
column="name"
></iqser-table-column-name>
<iqser-table-column-name
[label]="'dossier-templates-listing.table-col-names.created-by' | translate"
class="user-column"
></iqser-table-column-name>
<iqser-table-column-name
[label]="'dossier-templates-listing.table-col-names.created-on' | translate"
[withSort]="true"
column="dateAdded"
></iqser-table-column-name>
<iqser-table-column-name
[label]="'dossier-templates-listing.table-col-names.modified-on' | translate"
[withSort]="true"
column="dateModified"
></iqser-table-column-name>
<div class="scrollbar-placeholder"></div>
</div>
</redaction-table-header>
<redaction-empty-state
*ngIf="entitiesService.noData$ | async"
@ -132,3 +90,13 @@
</div>
</div>
</section>
<ng-template #bulkActions>
<iqser-circle-button
(action)="openBulkDeleteTemplatesDialog($event)"
*ngIf="canBulkDelete$(currentUser.isAdmin) | async"
[tooltip]="'dossier-templates-listing.bulk.delete' | translate"
[type]="circleButtonTypes.dark"
icon="red:trash"
></iqser-circle-button>
</ng-template>

View File

@ -5,9 +5,10 @@ import { AdminDialogService } from '../../services/admin-dialog.service';
import { DossierTemplateModelWrapper } from '@models/file/dossier-template-model.wrapper';
import { LoadingService } from '@services/loading.service';
import { DossierTemplateControllerService } from '@redaction/red-ui-http';
import { CircleButtonTypes, IconButtonTypes } from '@iqser/common-ui';
import { CircleButtonTypes, IconButtonTypes, TableColumnConfig } from '@iqser/common-ui';
import { BaseListingComponent, DefaultListingServices } from '@shared/base/base-listing.component';
import { UserService } from '@services/user.service';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
@Component({
templateUrl: './dossier-templates-listing-screen.component.html',
@ -20,6 +21,25 @@ export class DossierTemplatesListingScreenComponent extends BaseListingComponent
readonly iconButtonTypes = IconButtonTypes;
readonly circleButtonTypes = CircleButtonTypes;
readonly currentUser = this._userService.currentUser;
readonly tableHeaderLabel = _('dossier-templates-listing.table-header.title');
tableColumnConfigs: TableColumnConfig<DossierTemplateModelWrapper>[] = [
{
label: _('dossier-templates-listing.table-col-names.name'),
withSort: true,
column: 'name'
},
{ label: _('dossier-templates-listing.table-col-names.created-by'), class: 'user-column' },
{
label: _('dossier-templates-listing.table-col-names.created-on'),
withSort: true,
column: 'dateAdded'
},
{
label: _('dossier-templates-listing.table-col-names.modified-on'),
withSort: true,
column: 'dateModified'
}
];
constructor(
protected readonly _injector: Injector,
@ -37,35 +57,32 @@ export class DossierTemplatesListingScreenComponent extends BaseListingComponent
this.loadDossierTemplatesData();
}
openDeleteTemplatesDialog($event?: MouseEvent) {
openBulkDeleteTemplatesDialog($event?: MouseEvent) {
return this._dialogService.openDialog('confirm', $event, null, async () => {
this._loadingService.start();
await this._dossierTemplateControllerService
.deleteDossierTemplates(this.entitiesService.selected.map(d => d.dossierTemplateId))
.toPromise();
this.entitiesService.setSelected([]);
await this._appStateService.loadAllDossierTemplates();
await this._appStateService.loadDictionaryData();
this.loadDossierTemplatesData();
this._loadingService.loadWhile(this._deleteTemplates());
});
}
loadDossierTemplatesData() {
this._loadingService.start();
this._appStateService.reset();
this.entitiesService.setEntities(this._appStateService.dossierTemplates);
this._loadDossierTemplateStats();
this._loadingService.stop();
}
openAddDossierTemplateDialog() {
this._dialogService.openDialog('addEditDossierTemplate', null, null, async newDossierTemplate => {
if (newDossierTemplate) {
this.loadDossierTemplatesData();
}
this._dialogService.openDialog('addEditDossierTemplate', null, null, () => {
this.loadDossierTemplatesData();
});
}
private async _deleteTemplates(templateIds: string[] = this.entitiesService.selected.map(d => d.dossierTemplateId)) {
await this._dossierTemplateControllerService.deleteDossierTemplates(templateIds).toPromise();
this.entitiesService.setSelected([]);
await this._appStateService.loadAllDossierTemplates();
await this._appStateService.loadDictionaryData();
this.loadDossierTemplatesData();
}
private _loadDossierTemplateStats() {
this.entitiesService.all.forEach(rs => {
const dictionaries = this._appStateService.dictionaryData[rs.dossierTemplateId];

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"
[tableColumnConfigs]="tableColumnConfigs"
[tableHeaderLabel]="tableHeaderLabel"
></redaction-table-header>
<redaction-empty-state
*ngIf="entitiesService.noData$ | async"
@ -99,22 +56,22 @@
<div>
<div class="small-label">
{{ getRestoreDate(entity.softDeletedTime) | date: 'timeFromNow' }}
{{ entity.restoreDate | date: 'timeFromNow' }}
</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="entitiesService.areSomeSelected$ | 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

@ -4,15 +4,20 @@ import { Dossier } from '@redaction/red-ui-http';
import { LoadingService } from '@services/loading.service';
import { AppConfigKey, AppConfigService } from '@app-config/app-config.service';
import * as moment from 'moment';
import { CircleButtonTypes } from '@iqser/common-ui';
import { CircleButtonTypes, TableColumnConfig } from '@iqser/common-ui';
import { BaseListingComponent, DefaultListingServices } from '@shared/base/base-listing.component';
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';
import { getLeftDateTime } from '@utils/functions';
const HOURS_IN_A_DAY = 24;
const MINUTES_IN_AN_HOUR = 60;
interface DossierListItem extends Dossier {
readonly canRestore: boolean;
readonly restoreDate: string;
}
@Component({
templateUrl: './trash-screen.component.html',
@ -20,11 +25,34 @@ const MINUTES_IN_AN_HOUR = 60;
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [...DefaultListingServices, DossiersService]
})
export class TrashScreenComponent extends BaseListingComponent<Dossier> implements OnInit {
export class TrashScreenComponent extends BaseListingComponent<DossierListItem> implements OnInit {
readonly itemSize = 80;
protected readonly _primaryKey = 'dossierName';
readonly circleButtonTypes = CircleButtonTypes;
readonly tableHeaderLabel = 'trash.table-header.title';
readonly canRestoreSelected$ = this._canRestoreSelected$;
private readonly _deleteRetentionHours = this._appConfigService.getConfig(AppConfigKey.DELETE_RETENTION_HOURS);
readonly tableColumnConfigs: TableColumnConfig<DossierListItem>[] = [
{
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'
}
];
constructor(
readonly permissionsService: PermissionsService,
@ -37,52 +65,24 @@ export class TrashScreenComponent extends BaseListingComponent<Dossier> implemen
super(_injector);
}
private get _canRestoreSelected$(): Observable<boolean> {
return this.entitiesService.selected$.pipe(
map(entities => entities.length && !entities.find(dossier => !dossier.canRestore)),
distinctUntilChanged()
);
}
async ngOnInit(): Promise<void> {
this._loadingService.start();
await this.loadDossierTemplatesData();
await this._loadDossiersData();
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;
}
getRestoreDate(softDeletedTime: string): string {
private _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> {
const period = this._appConfigService.getConfig('DELETE_RETENTION_HOURS');
hardDelete(dossiers = this.entitiesService.selected) {
const data = new ConfirmationDialogInput({
title: dossiers.length > 1 ? _('confirmation-dialog.delete-dossier.title-alt') : _('confirmation-dialog.delete-dossier.title'),
titleColor: TitleColors.PRIMARY,
@ -91,17 +91,55 @@ export class TrashScreenComponent extends BaseListingComponent<Dossier> implemen
confirmationText: _('confirmation-dialog.delete-dossier.confirmation-text'),
requireInput: true,
denyText: _('confirmation-dialog.delete-dossier.deny-text'),
translateParams: { dossierName: dossiers[0].dossierName, period: period }
translateParams: {
dossierName: dossiers[0].dossierName,
period: this._deleteRetentionHours
}
});
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.entitiesService.selected) {
this._loadingService.loadWhile(this._restore(dossiers));
}
private async _loadDossiersData(): Promise<void> {
this.entitiesService.setEntities(this._toListItems(await this._dossiersService.getDeleted()));
}
private _canRestoreDossier(restoreDate: string): boolean {
const { daysLeft, hoursLeft, minutesLeft } = getLeftDateTime(restoreDate);
return daysLeft >= 0 && hoursLeft >= 0 && minutesLeft > 0;
}
private _toListItems(dossiers: Dossier[]): DossierListItem[] {
return dossiers.map(dossier => this._toListItem(dossier));
}
private _toListItem(dossier: Dossier): DossierListItem {
const restoreDate = this._getRestoreDate(dossier.softDeletedTime);
return {
...dossier,
restoreDate,
canRestore: this._canRestoreDossier(restoreDate)
};
}
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

@ -13,9 +13,17 @@
<ng-container [ngTemplateOutlet]="bulkActions"></ng-container>
<redaction-quick-filters></redaction-quick-filters>
<!-- Custom content-->
<ng-content></ng-content>
</div>
<div [class.selection-enabled]="selectionEnabled" class="table-header" redactionSyncWidth="table-item">
<div
[class.no-data]="entitiesService.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

@ -3,9 +3,8 @@ import * as moment from 'moment';
import { TranslateService } from '@ngx-translate/core';
import { DatePipe as BaseDatePipe } from '@angular/common';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { getLeftDateTime } from '@utils/functions';
const HOURS_IN_A_DAY = 24;
const MINUTES_IN_AN_HOUR = 60;
const MONTH_NAMES = {
0: _('months.jan'),
1: _('months.feb'),
@ -38,15 +37,8 @@ export class DatePipe extends BaseDatePipe implements PipeTransform {
return super.transform(value, format, timezone, locale);
}
private _getTimeFromNow(item: string) {
const date = moment(item);
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;
private _getTimeFromNow(value: string) {
const { daysLeft, hoursLeft, minutesLeft } = getLeftDateTime(value);
if (daysLeft === 0 && hoursLeft === 0 && minutesLeft > 0) return this._translateService.instant('time.less-than-an-hour');

View File

@ -1,3 +1,5 @@
import * as moment from 'moment';
export const FALLBACK_COLOR = '#CCCCCC';
export function groupBy(xs: any[], key: string) {
@ -67,3 +69,19 @@ export function toNumber(string) {
return 0;
}
}
const HOURS_IN_A_DAY = 24;
const MINUTES_IN_AN_HOUR = 60;
export function getLeftDateTime(ISOString: string) {
const date = moment(ISOString);
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, hoursLeft, minutesLeft };
}

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