diff --git a/apps/red-ui/src/app/components/downloads-list-screen/downloads-list-screen.component.html b/apps/red-ui/src/app/components/downloads-list-screen/downloads-list-screen.component.html index 8007a30fd..96540c481 100644 --- a/apps/red-ui/src/app/components/downloads-list-screen/downloads-list-screen.component.html +++ b/apps/red-ui/src/app/components/downloads-list-screen/downloads-list-screen.component.html @@ -7,7 +7,7 @@ [bulkActions]="bulkActions" [hasEmptyColumn]="true" [selectionEnabled]="true" - [tableColumnConfigs]="tableColConfigs" + [tableColumnConfigs]="tableColumnConfigs" [tableHeaderLabel]="tableHeaderLabel" > diff --git a/apps/red-ui/src/app/components/downloads-list-screen/downloads-list-screen.component.ts b/apps/red-ui/src/app/components/downloads-list-screen/downloads-list-screen.component.ts index cd2ea8848..db4e6259c 100644 --- a/apps/red-ui/src/app/components/downloads-list-screen/downloads-list-screen.component.ts +++ b/apps/red-ui/src/app/components/downloads-list-screen/downloads-list-screen.component.ts @@ -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 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[] = [ + readonly tableColumnConfigs: TableColumnConfig[] = [ { 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 d.storageId); await this._downloadControllerService.deleteDownload({ storageIds }).toPromise(); + this.entitiesService.setSelected([]); await this._loadData(); } diff --git a/apps/red-ui/src/app/modules/admin/dialogs/add-edit-dossier-template-dialog/add-edit-dossier-template-dialog.component.ts b/apps/red-ui/src/app/modules/admin/dialogs/add-edit-dossier-template-dialog/add-edit-dossier-template-dialog.component.ts index 3cdb65db1..5d5fdc909 100644 --- a/apps/red-ui/src/app/modules/admin/dialogs/add-edit-dossier-template-dialog/add-edit-dossier-template-dialog.component.ts +++ b/apps/red-ui/src/app/modules/admin/dialogs/add-edit-dossier-template-dialog/add-edit-dossier-template-dialog.component.ts @@ -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 { diff --git a/apps/red-ui/src/app/modules/admin/screens/dossier-template-listing/dossier-templates-listing-screen.component.html b/apps/red-ui/src/app/modules/admin/screens/dossier-template-listing/dossier-templates-listing-screen.component.html index bf3379f53..f956818ae 100644 --- a/apps/red-ui/src/app/modules/admin/screens/dossier-template-listing/dossier-templates-listing-screen.component.html +++ b/apps/red-ui/src/app/modules/admin/screens/dossier-template-listing/dossier-templates-listing-screen.component.html @@ -8,28 +8,12 @@
-
- - - - {{ - 'dossier-templates-listing.table-header.title' - | translate: { length: (entitiesService.displayedLength$ | async) } - }} - - - - +
-
- -
-
- - - - - - -
-
+
+ + + + diff --git a/apps/red-ui/src/app/modules/admin/screens/dossier-template-listing/dossier-templates-listing-screen.component.ts b/apps/red-ui/src/app/modules/admin/screens/dossier-template-listing/dossier-templates-listing-screen.component.ts index 430ceddf0..373bba82b 100644 --- a/apps/red-ui/src/app/modules/admin/screens/dossier-template-listing/dossier-templates-listing-screen.component.ts +++ b/apps/red-ui/src/app/modules/admin/screens/dossier-template-listing/dossier-templates-listing-screen.component.ts @@ -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[] = [ + { + 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]; diff --git a/apps/red-ui/src/app/modules/admin/screens/trash/trash-screen.component.html b/apps/red-ui/src/app/modules/admin/screens/trash/trash-screen.component.html index ae24d7d02..7f8c0234b 100644 --- a/apps/red-ui/src/app/modules/admin/screens/trash/trash-screen.component.html +++ b/apps/red-ui/src/app/modules/admin/screens/trash/trash-screen.component.html @@ -5,55 +5,12 @@
-
- - - - {{ 'trash.table-header.title' | translate: { length: (entitiesService.displayedLength$ | async) } }} - - - - - -
- -
-
- - - - - -
-
+
- {{ getRestoreDate(entity.softDeletedTime) | date: 'timeFromNow' }} + {{ entity.restoreDate | date: 'timeFromNow' }}
@@ -125,3 +82,23 @@
+ + +
+ + + +
+
diff --git a/apps/red-ui/src/app/modules/admin/screens/trash/trash-screen.component.scss b/apps/red-ui/src/app/modules/admin/screens/trash/trash-screen.component.scss index 41c1395e0..9bebde5ca 100644 --- a/apps/red-ui/src/app/modules/admin/screens/trash/trash-screen.component.scss +++ b/apps/red-ui/src/app/modules/admin/screens/trash/trash-screen.component.scss @@ -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; } } diff --git a/apps/red-ui/src/app/modules/admin/screens/trash/trash-screen.component.ts b/apps/red-ui/src/app/modules/admin/screens/trash/trash-screen.component.ts index e26cf9d5d..93e074a6b 100644 --- a/apps/red-ui/src/app/modules/admin/screens/trash/trash-screen.component.ts +++ b/apps/red-ui/src/app/modules/admin/screens/trash/trash-screen.component.ts @@ -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 implements OnInit { +export class TrashScreenComponent extends BaseListingComponent 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[] = [ + { + 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 implemen super(_injector); } + private get _canRestoreSelected$(): Observable { + return this.entitiesService.selected$.pipe( + map(entities => entities.length && !entities.find(dossier => !dossier.canRestore)), + distinctUntilChanged() + ); + } + async ngOnInit(): Promise { this._loadingService.start(); - - await this.loadDossierTemplatesData(); - + await this._loadDossiersData(); this._loadingService.stop(); } - async loadDossierTemplatesData(): Promise { - 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 { - this._loadingService.start(); - await this._dossiersService.restore(dossierIds); - this._removeFromList(dossierIds); - this._loadingService.stop(); - } - - private async _hardDelete(dossiers: Dossier[]): Promise { - 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 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 { + 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 { + 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); diff --git a/apps/red-ui/src/app/modules/dossier/screens/dossier-listing-screen/dossier-listing-screen.component.ts b/apps/red-ui/src/app/modules/dossier/screens/dossier-listing-screen/dossier-listing-screen.component.ts index 1632d77ee..d270095ce 100644 --- a/apps/red-ui/src/app/modules/dossier/screens/dossier-listing-screen/dossier-listing-screen.component.ts +++ b/apps/red-ui/src/app/modules/dossier/screens/dossier-listing-screen/dossier-listing-screen.component.ts @@ -41,7 +41,7 @@ export class DossierListingScreenComponent extends BaseListingComponent 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'); diff --git a/apps/red-ui/src/app/modules/shared/components/table-header/table-header.component.html b/apps/red-ui/src/app/modules/shared/components/table-header/table-header.component.html index 447aaded7..abec8eb74 100644 --- a/apps/red-ui/src/app/modules/shared/components/table-header/table-header.component.html +++ b/apps/red-ui/src/app/modules/shared/components/table-header/table-header.component.html @@ -13,9 +13,17 @@ + + + -
+
0) return this._translateService.instant('time.less-than-an-hour'); diff --git a/apps/red-ui/src/app/utils/functions.ts b/apps/red-ui/src/app/utils/functions.ts index 183aae547..1bf96c3b0 100644 --- a/apps/red-ui/src/app/utils/functions.ts +++ b/apps/red-ui/src/app/utils/functions.ts @@ -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 }; +} diff --git a/apps/red-ui/src/assets/config/config.json b/apps/red-ui/src/assets/config/config.json index 4b001a0b1..6cfc820ed 100644 --- a/apps/red-ui/src/assets/config/config.json +++ b/apps/red-ui/src/assets/config/config.json @@ -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", diff --git a/apps/red-ui/src/assets/icons/general/enter.svg b/apps/red-ui/src/assets/icons/general/enter.svg index af96126ca..d7cdfd6fb 100644 --- a/apps/red-ui/src/assets/icons/general/enter.svg +++ b/apps/red-ui/src/assets/icons/general/enter.svg @@ -1,11 +1,10 @@ - - B99F3D5E-879A-47E3-A8C7-84877EE418F3 - + diff --git a/apps/red-ui/src/assets/icons/general/put-back.svg b/apps/red-ui/src/assets/icons/general/put-back.svg index 96da3dae5..90c10a820 100644 --- a/apps/red-ui/src/assets/icons/general/put-back.svg +++ b/apps/red-ui/src/assets/icons/general/put-back.svg @@ -1,7 +1,6 @@ - 711C9D82-CAA8-47BE-954A-A9DA22CE85E6