diff --git a/apps/red-ui/src/app/guards/trash-dossiers.guard.ts b/apps/red-ui/src/app/guards/trash-dossiers.guard.ts deleted file mode 100644 index d1a8c1a62..000000000 --- a/apps/red-ui/src/app/guards/trash-dossiers.guard.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Injectable } from '@angular/core'; -import { CanActivate } from '@angular/router'; -import { firstValueFrom } from 'rxjs'; -import { TrashDossiersService } from '@services/entity-services/trash-dossiers.service'; - -@Injectable({ providedIn: 'root' }) -export class TrashDossiersGuard implements CanActivate { - constructor(private readonly _trashDossiersService: TrashDossiersService) {} - - async canActivate(): Promise { - await firstValueFrom(this._trashDossiersService.loadAll()); - return true; - } -} diff --git a/apps/red-ui/src/app/modules/admin/admin-routing.module.ts b/apps/red-ui/src/app/modules/admin/admin-routing.module.ts index 8595b149b..785351d42 100644 --- a/apps/red-ui/src/app/modules/admin/admin-routing.module.ts +++ b/apps/red-ui/src/app/modules/admin/admin-routing.module.ts @@ -22,7 +22,8 @@ import { DICTIONARY_TYPE, DOSSIER_TEMPLATE_ID } from '@utils/constants'; import { DossierTemplateExistsGuard } from '@guards/dossier-template-exists.guard'; import { DictionaryExistsGuard } from '@guards/dictionary-exists.guard'; import { DossierStatesListingScreenComponent } from './screens/dossier-states-listing/dossier-states-listing-screen.component'; -import { TrashDossiersGuard } from '@guards/trash-dossiers.guard'; +import { DossiersGuard } from '@guards/dossiers.guard'; +import { ACTIVE_DOSSIERS_SERVICE } from '../../tokens'; const routes: Routes = [ { path: '', redirectTo: 'dossier-templates', pathMatch: 'full' }, @@ -200,8 +201,9 @@ const routes: Routes = [ component: TrashScreenComponent, canActivate: [CompositeRouteGuard], data: { - routeGuards: [AuthGuard, RedRoleGuard, TrashDossiersGuard], + routeGuards: [AuthGuard, RedRoleGuard, DossiersGuard], requiredRoles: ['RED_MANAGER'], + dossiersService: ACTIVE_DOSSIERS_SERVICE, }, }, ]; diff --git a/apps/red-ui/src/app/modules/admin/admin.module.ts b/apps/red-ui/src/app/modules/admin/admin.module.ts index d3e083a57..03e0332fa 100644 --- a/apps/red-ui/src/app/modules/admin/admin.module.ts +++ b/apps/red-ui/src/app/modules/admin/admin.module.ts @@ -48,6 +48,7 @@ import { DossierStatesListingScreenComponent } from './screens/dossier-states-li import { AddEditDossierStateDialogComponent } from './dialogs/add-edit-dossier-state-dialog/add-edit-dossier-state-dialog.component'; import { A11yModule } from '@angular/cdk/a11y'; import { ConfirmDeleteDossierStateDialogComponent } from './dialogs/confirm-delete-dossier-state-dialog/confirm-delete-dossier-state-dialog.component'; +import { TrashTableItemComponent } from './screens/trash/trash-table-item/trash-table-item.component'; const dialogs = [ AddEditDossierTemplateDialogComponent, @@ -100,6 +101,7 @@ const components = [ DossierStatesListingScreenComponent, AddEditDossierStateDialogComponent, ConfirmDeleteDossierStateDialogComponent, + TrashTableItemComponent, ], providers: [AdminDialogService, AuditService, DigitalSignatureService, LicenseReportService, RulesService, SmtpConfigService], imports: [ diff --git a/apps/red-ui/src/app/modules/admin/screens/dossier-states-listing/dossier-states-listing-screen.component.ts b/apps/red-ui/src/app/modules/admin/screens/dossier-states-listing/dossier-states-listing-screen.component.ts index 8411daf27..3d7ff95bd 100644 --- a/apps/red-ui/src/app/modules/admin/screens/dossier-states-listing/dossier-states-listing-screen.component.ts +++ b/apps/red-ui/src/app/modules/admin/screens/dossier-states-listing/dossier-states-listing-screen.component.ts @@ -9,12 +9,12 @@ import { } from '@iqser/common-ui'; import { DossierState, IDossierState } from '@red/domain'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; -import { firstValueFrom, map, Observable } from 'rxjs'; +import { firstValueFrom, Observable } from 'rxjs'; import { AdminDialogService } from '../../services/admin-dialog.service'; import { DoughnutChartConfig } from '@shared/components/simple-doughnut-chart/simple-doughnut-chart.component'; import { ActivatedRoute } from '@angular/router'; import { DossierStatesMapService } from '@services/entity-services/dossier-states-map.service'; -import { tap } from 'rxjs/operators'; +import { map, tap } from 'rxjs/operators'; import { PermissionsService } from '@services/permissions.service'; import { DossierStatesService } from '@services/entity-services/dossier-states.service'; 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 35c955de3..1f641e133 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 @@ -42,58 +42,5 @@ -
-
-
- {{ entity.dossierName }} -
-
-
- - {{ entity.memberIds.length }} -
-
- - {{ entity.date | date: 'mediumDate' }} -
-
- - {{ entity.dueDate | date: 'mediumDate' }} -
-
-
- -
- -
- -
- - {{ entity.softDeletedTime | date: 'exactDate' }} - -
- -
-
- {{ entity.restoreDate | date: 'timeFromNow' }} -
-
- - - -
-
-
+
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 188ad23c7..335e5c128 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 @@ -1,9 +1,8 @@ -import { ChangeDetectionStrategy, Component, forwardRef, Injector } from '@angular/core'; +import { ChangeDetectionStrategy, Component, forwardRef, Injector, OnInit } from '@angular/core'; import { CircleButtonTypes, ConfirmationDialogInput, - DefaultListingServicesTmp, - EntitiesService, + DefaultListingServices, ListingComponent, LoadingService, SortingOrders, @@ -12,49 +11,49 @@ import { } from '@iqser/common-ui'; import { AdminDialogService } from '../../services/admin-dialog.service'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; -import { Observable } from 'rxjs'; +import { firstValueFrom, Observable } from 'rxjs'; import { distinctUntilChanged, map } from 'rxjs/operators'; import { RouterHistoryService } from '@services/router-history.service'; -import { TrashDossier } from '@red/domain'; -import { TrashDossiersService } from '@services/entity-services/trash-dossiers.service'; +import { TrashItem } from '@red/domain'; +import { TrashService } from '@services/entity-services/trash.service'; +import { FilesService } from '@services/entity-services/files.service'; +import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service'; +import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service'; @Component({ templateUrl: './trash-screen.component.html', styleUrls: ['./trash-screen.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, - providers: [ - ...DefaultListingServicesTmp, - { - provide: EntitiesService, - useExisting: TrashDossiersService, - }, - { provide: ListingComponent, useExisting: forwardRef(() => TrashScreenComponent) }, - ], + providers: [...DefaultListingServices, { provide: ListingComponent, useExisting: forwardRef(() => TrashScreenComponent) }], }) -export class TrashScreenComponent extends ListingComponent { +export class TrashScreenComponent extends ListingComponent implements OnInit { readonly circleButtonTypes = CircleButtonTypes; readonly tableHeaderLabel = _('trash.table-header.title'); readonly canRestoreSelected$ = this._canRestoreSelected$; readonly canHardDeleteSelected$ = this._canHardDeleteSelected$; - readonly tableColumnConfigs: TableColumnConfig[] = [ - { label: _('trash.table-col-names.name'), sortByKey: 'searchKey' }, + readonly tableColumnConfigs: TableColumnConfig[] = [ + { label: _('trash.table-col-names.name') }, { label: _('trash.table-col-names.owner'), class: 'user-column' }, - { label: _('trash.table-col-names.deleted-on'), sortByKey: 'softDeletedTime' }, - { label: _('trash.table-col-names.time-to-restore'), sortByKey: 'softDeletedTime' }, + { label: _('trash.table-col-names.dossier') }, + { label: _('trash.table-col-names.deleted-on') }, + { label: _('trash.table-col-names.time-to-restore') }, ]; constructor( protected readonly _injector: Injector, private readonly _loadingService: LoadingService, - private readonly _trashDossiersService: TrashDossiersService, + private readonly _trashService: TrashService, + private readonly _activeDossiersService: ActiveDossiersService, + private readonly _filesService: FilesService, readonly routerHistoryService: RouterHistoryService, private readonly _adminDialogService: AdminDialogService, + private readonly _dossierTemplatesService: DossierTemplatesService, ) { super(_injector); this.sortingService.setSortingOption({ - column: 'softDeletedTime', - order: SortingOrders.desc, + column: 'type', + order: SortingOrders.asc, }); } @@ -72,26 +71,38 @@ export class TrashScreenComponent extends ListingComponent { ); } - disabledFn = (dossier: TrashDossier) => !dossier.canRestore; + async ngOnInit(): Promise { + this._loadingService.start(); + await firstValueFrom(this._dossierTemplatesService.loadAll()); + const entities: TrashItem[] = await firstValueFrom(this._trashService.all()); + this.entitiesService.setEntities(entities); + this._loadingService.stop(); + } - hardDelete(dossiers = this.listingService.selected): void { + disabledFn = (dossier: TrashItem) => !dossier.canRestore; + + hardDelete(items = this.listingService.selected): void { const data = new ConfirmationDialogInput({ - title: _('confirmation-dialog.delete-dossier.title'), + title: _('confirmation-dialog.delete-items.title'), titleColor: TitleColors.WARN, - question: _('confirmation-dialog.delete-dossier.question'), + question: _('confirmation-dialog.delete-items.question'), translateParams: { - dossierName: dossiers[0].dossierName, - dossiersCount: dossiers.length, + name: items[0].name, + itemsCount: items.length, }, }); - this._adminDialogService.openDialog('confirm', null, data, () => { - const dossierIds: string[] = dossiers.map(d => d.id); - this._loadingService.loadWhile(this._trashDossiersService.hardDelete(dossierIds)); + this._adminDialogService.openDialog('confirm', null, data, async () => { + this._loadingService.start(); + await firstValueFrom(this._trashService.hardDelete(items)); + items.forEach(item => this.entitiesService.remove(item.id)); + this._loadingService.stop(); }); } - restore(dossiers = this.listingService.selected): void { - const dossierIds: string[] = dossiers.map(d => d.id); - this._loadingService.loadWhile(this._trashDossiersService.restore(dossierIds)); + async restore(items = this.listingService.selected): Promise { + this._loadingService.start(); + await firstValueFrom(this._trashService.restore(items)); + items.forEach(item => this.entitiesService.remove(item.id)); + this._loadingService.stop(); } } diff --git a/apps/red-ui/src/app/modules/admin/screens/trash/trash-table-item/trash-table-item.component.html b/apps/red-ui/src/app/modules/admin/screens/trash/trash-table-item/trash-table-item.component.html new file mode 100644 index 000000000..39d3eacbd --- /dev/null +++ b/apps/red-ui/src/app/modules/admin/screens/trash/trash-table-item/trash-table-item.component.html @@ -0,0 +1,57 @@ +
+ +
+ + + +
+
+ +
+ +
+ + + +
+ + {{ item.softDeletedTime | date: 'exactDate' }} + +
+ +
+
+ {{ item.restoreDate | date: 'timeFromNow' }} +
+
+ + + +
+
diff --git a/apps/red-ui/src/app/modules/admin/screens/trash/trash-table-item/trash-table-item.component.scss b/apps/red-ui/src/app/modules/admin/screens/trash/trash-table-item/trash-table-item.component.scss new file mode 100644 index 000000000..8445bc1fe --- /dev/null +++ b/apps/red-ui/src/app/modules/admin/screens/trash/trash-table-item/trash-table-item.component.scss @@ -0,0 +1,13 @@ +.filename { + flex-direction: row; + align-items: center; + justify-content: flex-start; + + > mat-icon { + min-width: 24px; + + &.low-opacity { + opacity: 0.2; + } + } +} diff --git a/apps/red-ui/src/app/modules/admin/screens/trash/trash-table-item/trash-table-item.component.ts b/apps/red-ui/src/app/modules/admin/screens/trash/trash-table-item/trash-table-item.component.ts new file mode 100644 index 000000000..06190de02 --- /dev/null +++ b/apps/red-ui/src/app/modules/admin/screens/trash/trash-table-item/trash-table-item.component.ts @@ -0,0 +1,41 @@ +import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, Output } from '@angular/core'; +import { Dossier, DossierStats, TrashDossier, TrashFile, TrashItem } from '@red/domain'; +import { CircleButtonTypes } from '@iqser/common-ui'; +import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service'; +import { Observable } from 'rxjs'; +import { DossierStatsService } from '@services/dossiers/dossier-stats.service'; + +@Component({ + selector: 'redaction-trash-table-item [item]', + templateUrl: './trash-table-item.component.html', + styleUrls: ['./trash-table-item.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class TrashTableItemComponent implements OnChanges { + readonly circleButtonTypes = CircleButtonTypes; + + @Input() item: TrashItem; + @Output() restore = new EventEmitter(); + @Output() hardDelete = new EventEmitter(); + fileDossier$: Observable; + dossierStats$: Observable; + + constructor(private readonly _activeDossiersService: ActiveDossiersService, readonly dossierStatsService: DossierStatsService) {} + + file(item: TrashItem): TrashFile { + return item as TrashFile; + } + + dossier(item: TrashItem): TrashDossier { + return item as TrashDossier; + } + + ngOnChanges(): void { + if (this.item.isFile) { + this.fileDossier$ = this._activeDossiersService.getEntityChanged$(this.file(this.item).dossierId); + } + if (this.item.isDossier) { + this.dossierStats$ = this.dossierStatsService.watch$(this.dossier(this.item).id); + } + } +} diff --git a/apps/red-ui/src/app/modules/archive/components/table-item/table-item.component.html b/apps/red-ui/src/app/modules/archive/components/table-item/table-item.component.html index aabfd8229..e259f3edc 100644 --- a/apps/red-ui/src/app/modules/archive/components/table-item/table-item.component.html +++ b/apps/red-ui/src/app/modules/archive/components/table-item/table-item.component.html @@ -1,5 +1,5 @@
- +
{{ dossier.archivedTime | date: 'd MMM. yyyy' }}
diff --git a/apps/red-ui/src/app/modules/dossier-overview/components/dossier-details-stats/dossier-details-stats.component.html b/apps/red-ui/src/app/modules/dossier-overview/components/dossier-details-stats/dossier-details-stats.component.html index ce35f1ea8..85500bec8 100644 --- a/apps/red-ui/src/app/modules/dossier-overview/components/dossier-details-stats/dossier-details-stats.component.html +++ b/apps/red-ui/src/app/modules/dossier-overview/components/dossier-details-stats/dossier-details-stats.component.html @@ -20,33 +20,34 @@ {{ 'dossier-overview.dossier-details.stats.analysed-pages' | translate: { count: stats.numberOfPages | number } }} + +
+ + {{ 'dossier-overview.dossier-details.stats.created-on' | translate: { date: date } }} +
+ +
+ + {{ 'dossier-overview.dossier-details.stats.due-date' | translate: { date: dueDate } }} +
+ +
+ + {{ dossierTemplateName }} +
+ + + + +
+ + {{ 'dossier-overview.dossier-details.stats.deleted' | translate: { count: stats.numberOfSoftDeletedFiles } }} +
-
- - {{ 'dossier-overview.dossier-details.stats.created-on' | translate: { date: date } }} -
- -
- - {{ 'dossier-overview.dossier-details.stats.due-date' | translate: { date: dueDate } }} -
- -
- - {{ dossierTemplateName }} -
- - - - -
; dossierStats$: Observable; constructor( @@ -28,15 +25,10 @@ export class DossierDetailsStatsComponent implements OnInit { private readonly _dialogService: DossiersDialogService, private readonly _filesService: FilesService, private readonly _dossierStatsService: DossierStatsService, - private readonly _filesMapService: FilesMapService, ) {} ngOnInit() { this.dossierStats$ = this._dossierStatsService.watch$(this.dossier.dossierId); - this.deletedFilesCount$ = this._filesMapService.get$(this.dossier.dossierId).pipe( - switchMap(() => this._filesService.getDeletedFilesFor(this.dossier.id)), - map(files => files.length), - ); this.dossierTemplateName = this._dossierTemplatesService.find(this.dossier.dossierTemplateId)?.name || '-'; } diff --git a/apps/red-ui/src/app/modules/dossier-overview/components/screen-header/dossier-overview-screen-header.component.ts b/apps/red-ui/src/app/modules/dossier-overview/components/screen-header/dossier-overview-screen-header.component.ts index 903113470..666a68e48 100644 --- a/apps/red-ui/src/app/modules/dossier-overview/components/screen-header/dossier-overview-screen-header.component.ts +++ b/apps/red-ui/src/app/modules/dossier-overview/components/screen-header/dossier-overview-screen-header.component.ts @@ -10,7 +10,7 @@ import { SortingService, Toaster, } from '@iqser/common-ui'; -import { Dossier, File, IFile } from '@red/domain'; +import { Dossier, File } from '@red/domain'; import { PermissionsService } from '@services/permissions.service'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { ReanalysisService } from '@services/reanalysis.service'; @@ -69,7 +69,7 @@ export class DossierOverviewScreenHeaderComponent implements OnInit { const sortedEntities$ = this.listingService.displayed$.pipe(map(entities => this.sortingService.defaultSort(entities))); sortedEntities$.pipe(take(1)).subscribe(entities => { const fileName = this.dossier.dossierName + '.export.csv'; - const mapper = (file?: IFile) => ({ + const mapper = (file?: File) => ({ ...file, assignee: this._userService.getNameForId(file.assignee) || '-', primaryAttribute: this._primaryFileAttributeService.getPrimaryFileAttributeValue(file, this.dossier.dossierTemplateId), diff --git a/apps/red-ui/src/app/modules/dossier-overview/dossier-overview.module.ts b/apps/red-ui/src/app/modules/dossier-overview/dossier-overview.module.ts index 747d9bba6..9a0011201 100644 --- a/apps/red-ui/src/app/modules/dossier-overview/dossier-overview.module.ts +++ b/apps/red-ui/src/app/modules/dossier-overview/dossier-overview.module.ts @@ -11,11 +11,9 @@ import { DossierDetailsStatsComponent } from './components/dossier-details-stats import { TableItemComponent } from './components/table-item/table-item.component'; import { SharedDossiersModule } from '../dossier/shared/shared-dossiers.module'; import { FileWorkloadComponent } from './components/table-item/file-workload/file-workload.component'; -import { FileStatsComponent } from './components/file-stats/file-stats.component'; import { WorkflowItemComponent } from './components/workflow-item/workflow-item.component'; import { DossierOverviewScreenHeaderComponent } from './components/screen-header/dossier-overview-screen-header.component'; import { ViewModeSelectionComponent } from './components/view-mode-selection/view-mode-selection.component'; -import { FileNameColumnComponent } from './components/table-item/file-name-column/file-name-column.component'; const routes: Routes = [ { @@ -36,11 +34,9 @@ const routes: Routes = [ DossierDetailsStatsComponent, FileWorkloadComponent, TableItemComponent, - FileStatsComponent, WorkflowItemComponent, DossierOverviewScreenHeaderComponent, ViewModeSelectionComponent, - FileNameColumnComponent, ], imports: [RouterModule.forChild(routes), CommonModule, SharedModule, SharedDossiersModule, IqserIconsModule, TranslateModule], }) diff --git a/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/deleted-documents/edit-dossier-deleted-documents.component.html b/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/deleted-documents/edit-dossier-deleted-documents.component.html deleted file mode 100644 index ce1c54ddc..000000000 --- a/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/deleted-documents/edit-dossier-deleted-documents.component.html +++ /dev/null @@ -1,94 +0,0 @@ - - - -
-
- - - - - - - - -
-
- {{ file.filename }} -
- -
-
- - {{ file.numberOfPages }} -
-
- -
- -
- -
- -
- -
- {{ file.softDeleted | date: 'exactDate' }} -
- -
-
{{ file.restoreDate | date: 'timeFromNow' }}
-
- - - -
-
-
-
diff --git a/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/deleted-documents/edit-dossier-deleted-documents.component.scss b/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/deleted-documents/edit-dossier-deleted-documents.component.scss deleted file mode 100644 index 305ab6377..000000000 --- a/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/deleted-documents/edit-dossier-deleted-documents.component.scss +++ /dev/null @@ -1,16 +0,0 @@ -@use 'variables'; -@use 'common-mixins'; - -.instructions { - color: variables.$grey-7; - flex: 1; - text-align: end; -} - -:host ::ng-deep iqser-table cdk-virtual-scroll-viewport { - height: calc(100% - 81px) !important; -} - -.cell.filename span { - @include common-mixins.line-clamp(1); -} diff --git a/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/deleted-documents/edit-dossier-deleted-documents.component.ts b/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/deleted-documents/edit-dossier-deleted-documents.component.ts deleted file mode 100644 index 3f6b69cde..000000000 --- a/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/deleted-documents/edit-dossier-deleted-documents.component.ts +++ /dev/null @@ -1,181 +0,0 @@ -import { Component, forwardRef, Injector, Input, OnInit } from '@angular/core'; -import { EditDossierSaveResult, EditDossierSectionInterface } from '../edit-dossier-section.interface'; -import { Dossier, File, IFile } from '@red/domain'; -import { - CircleButtonTypes, - ConfirmationDialogInput, - DefaultListingServices, - getLeftDateTime, - IListable, - IRouterPath, - ListingComponent, - LoadingService, - SortingOrders, - TableColumnConfig, - TitleColors, -} from '@iqser/common-ui'; -import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; -import * as moment from 'moment'; -import { ConfigService } from '@services/config.service'; -import { firstValueFrom, Observable, of } from 'rxjs'; -import { distinctUntilChanged, map } from 'rxjs/operators'; -import { DossiersDialogService } from '../../../services/dossiers-dialog.service'; -import { FilesService } from '@services/entity-services/files.service'; -import { FileManagementService } from '@services/entity-services/file-management.service'; -import { workflowFileStatusTranslations } from '../../../../../translations/file-status-translations'; -import { PermissionsService } from '@services/permissions.service'; -import { UserService } from '@services/user.service'; - -interface FileListItem extends IFile, IListable, IRouterPath { - readonly canHardDelete: boolean; - readonly canRestore: boolean; - readonly restoreDate: string; -} - -@Component({ - selector: 'redaction-edit-dossier-deleted-documents', - templateUrl: './edit-dossier-deleted-documents.component.html', - styleUrls: ['./edit-dossier-deleted-documents.component.scss'], - providers: [ - ...DefaultListingServices, - { provide: ListingComponent, useExisting: forwardRef(() => EditDossierDeletedDocumentsComponent) }, - ], -}) -export class EditDossierDeletedDocumentsComponent extends ListingComponent implements EditDossierSectionInterface, OnInit { - readonly fileStatusTranslations = workflowFileStatusTranslations; - @Input() dossier: Dossier; - readonly changed = false; - readonly valid = false; - readonly canRestoreSelected$ = this._canRestoreSelected$; - readonly canDeleteSelected$ = this._canDeleteSelected$; - disabled: boolean; - readonly tableColumnConfigs: TableColumnConfig[] = [ - { label: _('edit-dossier-dialog.deleted-documents.table-col-names.name'), width: '3fr' }, - { label: _('edit-dossier-dialog.deleted-documents.table-col-names.pages') }, - { label: _('edit-dossier-dialog.deleted-documents.table-col-names.assignee'), class: 'user-column' }, - { label: _('edit-dossier-dialog.deleted-documents.table-col-names.status') }, - { label: _('edit-dossier-dialog.deleted-documents.table-col-names.deleted-on'), sortByKey: 'softDeleted', width: '2fr' }, - { label: _('edit-dossier-dialog.deleted-documents.table-col-names.time-to-restore'), sortByKey: 'softDeleted', width: '2fr' }, - ]; - readonly tableHeaderLabel = _('edit-dossier-dialog.deleted-documents.table-header.label'); - readonly circleButtonTypes = CircleButtonTypes; - readonly deleteRetentionHours = this._configService.values.DELETE_RETENTION_HOURS; - - constructor( - protected readonly _injector: Injector, - private readonly _fileManagementService: FileManagementService, - private readonly _filesService: FilesService, - private readonly _loadingService: LoadingService, - private readonly _configService: ConfigService, - private readonly _dialogService: DossiersDialogService, - private readonly _permissionsService: PermissionsService, - private readonly _userService: UserService, - ) { - super(_injector); - } - - private get _canRestoreSelected$(): Observable { - return this.listingService.selectedEntities$.pipe( - map(entities => entities.length && !entities.find(file => !file.canRestore)), - distinctUntilChanged(), - ); - } - - private get _canDeleteSelected$(): Observable { - return this.listingService.selectedEntities$.pipe( - map(entities => entities.length && !entities.find(file => !file.canHardDelete)), - distinctUntilChanged(), - ); - } - - hardDelete(files = this.listingService.selected) { - const data = new ConfirmationDialogInput({ - title: _('confirmation-dialog.permanently-delete-file.title'), - titleColor: TitleColors.WARN, - question: _('confirmation-dialog.permanently-delete-file.question'), - confirmationText: _('confirmation-dialog.permanently-delete-file.confirmation-text'), - requireInput: true, - denyText: _('confirmation-dialog.permanently-delete-file.deny-text'), - translateParams: { - fileName: files[0].filename, - filesCount: files.length, - }, - }); - this._dialogService.openDialog('confirm', null, data, () => { - this._loadingService.loadWhile(this._hardDelete(files)); - }); - } - - async ngOnInit() { - this._loadingService.start(); - const files = await firstValueFrom(this._filesService.getDeletedFilesFor(this.dossier.id)); - this.entitiesService.setEntities(this._toListItems(files)); - this.sortingService.setSortingOption({ - column: 'softDeleted', - order: SortingOrders.desc, - }); - this._loadingService.stop(); - } - - revert() {} - - save(): EditDossierSaveResult { - return firstValueFrom(of({ success: true })); - } - - restore(files = this.listingService.selected) { - this._loadingService.loadWhile(this._restore(files)); - } - - disabledFn = (file: FileListItem) => !file.canRestore; - - private async _restore(files: FileListItem[]): Promise { - const fileIds = files.map(f => f.id); - await firstValueFrom(this._fileManagementService.restore(files, this.dossier.id)); - this._removeFromList(fileIds); - } - - private async _hardDelete(files: FileListItem[]) { - const fileIds = files.map(f => f.id); - await firstValueFrom(this._fileManagementService.hardDelete(this.dossier.id, fileIds)); - this._removeFromList(fileIds); - } - - private _removeFromList(ids: string[]): void { - const entities = this.entitiesService.all.filter(e => !ids.includes(e.fileId)); - this.entitiesService.setEntities(entities); - } - - private _toListItems(files: IFile[]): FileListItem[] { - return files.map(file => this._toListItem(file)); - } - - private _toListItem(_file: IFile): FileListItem { - const file = new File(_file, this._userService.getNameForId(_file.assignee), this.dossier.routerPath); - const restoreDate = this._getRestoreDate(_file.softDeleted); - return { - id: file.fileId, - ...file, - restoreDate, - searchKey: file.filename, - canRestore: this._canRestore(file, restoreDate), - canHardDelete: this._canPerformActions(file), - }; - } - - private _canPerformActions(file: File): boolean { - return ( - this._permissionsService.canHardDeleteOrRestore(this.dossier) && - (this._userService.currentUser.isManager || this._permissionsService.canDeleteFile(file)) - ); - } - - private _canRestore(file: File, restoreDate: string): boolean { - const { daysLeft, hoursLeft, minutesLeft } = getLeftDateTime(restoreDate); - return this._canPerformActions(file) && daysLeft + hoursLeft + minutesLeft > 0; - } - - private _getRestoreDate(softDeletedTime: string): string { - return moment(softDeletedTime).add(this.deleteRetentionHours, 'hours').toISOString(); - } -} diff --git a/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/edit-dossier-dialog.component.html b/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/edit-dossier-dialog.component.html index 22872775f..946d699e7 100644 --- a/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/edit-dossier-dialog.component.html +++ b/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/edit-dossier-dialog.component.html @@ -43,11 +43,6 @@ *ngIf="activeNav === 'dossierAttributes'" [dossier]="dossier" > - -
diff --git a/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/edit-dossier-dialog.component.ts b/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/edit-dossier-dialog.component.ts index dafce681c..2a0c7bcaa 100644 --- a/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/edit-dossier-dialog.component.ts +++ b/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/edit-dossier-dialog.component.ts @@ -9,7 +9,6 @@ import { EditDossierDictionaryComponent } from './dictionary/edit-dossier-dictio import { EditDossierAttributesComponent } from './attributes/edit-dossier-attributes.component'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; -import { EditDossierDeletedDocumentsComponent } from './deleted-documents/edit-dossier-deleted-documents.component'; import { Observable } from 'rxjs'; import { tap } from 'rxjs/operators'; import { EditDossierTeamComponent } from './edit-dossier-team/edit-dossier-team.component'; @@ -18,7 +17,7 @@ import { UserService } from '@services/user.service'; import { DossiersService } from '@services/dossiers/dossiers.service'; import { dossiersServiceProvider } from '@services/entity-services/dossiers.service.provider'; -type Section = 'dossierInfo' | 'downloadPackage' | 'dossierDictionary' | 'members' | 'dossierAttributes' | 'deletedDocuments'; +type Section = 'dossierInfo' | 'downloadPackage' | 'dossierDictionary' | 'members' | 'dossierAttributes'; interface NavItem { key: Section; @@ -43,7 +42,6 @@ export class EditDossierDialogComponent extends BaseDialogComponent implements A @ViewChild(EditDossierDictionaryComponent) dictionaryComponent: EditDossierDictionaryComponent; @ViewChild(EditDossierTeamComponent) membersComponent: EditDossierTeamComponent; @ViewChild(EditDossierAttributesComponent) attributesComponent: EditDossierAttributesComponent; - @ViewChild(EditDossierDeletedDocumentsComponent) deletedDocumentsComponent: EditDossierDeletedDocumentsComponent; private _dossier: Dossier; @@ -83,22 +81,21 @@ export class EditDossierDialogComponent extends BaseDialogComponent implements A dossierDictionary: this.dictionaryComponent, members: this.membersComponent, dossierAttributes: this.attributesComponent, - deletedDocuments: this.deletedDocumentsComponent, }[this.activeNav]; } get noPaddingTab(): boolean { - return ['dossierAttributes', 'deletedDocuments'].includes(this.activeNav); + return ['dossierAttributes'].includes(this.activeNav); } get showHeading(): boolean { - return !['dossierAttributes', 'dossierDictionary', 'deletedDocuments'].includes(this.activeNav); + return !['dossierAttributes', 'dossierDictionary'].includes(this.activeNav); } get showActionButtons(): boolean { return ( (['members'].includes(this.activeNav) && this._userService.currentUser.isManager) || - (!['deletedDocuments'].includes(this.activeNav) && this._permissionsService.canEditDossier(this._dossier)) + this._permissionsService.canEditDossier(this._dossier) ); } @@ -184,10 +181,6 @@ export class EditDossierDialogComponent extends BaseDialogComponent implements A title: _('edit-dossier-dialog.nav-items.dossier-attributes'), readonly: !this._permissionsService.canEditDossierAttributes(this._dossier), }, - { - key: 'deletedDocuments', - sideNavTitle: _('edit-dossier-dialog.nav-items.deleted-documents'), - }, ]; } } diff --git a/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/general-info/edit-dossier-general-info.component.ts b/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/general-info/edit-dossier-general-info.component.ts index 4f8180d37..e4a295344 100644 --- a/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/general-info/edit-dossier-general-info.component.ts +++ b/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/general-info/edit-dossier-general-info.component.ts @@ -16,7 +16,7 @@ import { firstValueFrom } from 'rxjs'; import { DOSSIER_TEMPLATE_ID } from '@utils/constants'; import { TranslateService } from '@ngx-translate/core'; import { DossiersService } from '@services/dossiers/dossiers.service'; -import { TrashDossiersService } from '@services/entity-services/trash-dossiers.service'; +import { TrashService } from '@services/entity-services/trash.service'; import { ArchivedDossiersService } from '@services/dossiers/archived-dossiers.service'; import { DossierStatesMapService } from '@services/entity-services/dossier-states-map.service'; @@ -42,7 +42,7 @@ export class EditDossierGeneralInfoComponent implements OnInit, EditDossierSecti private readonly _dossierStatesMapService: DossierStatesMapService, private readonly _dossierTemplatesService: DossierTemplatesService, private readonly _dossiersService: DossiersService, - private readonly _trashDossiersService: TrashDossiersService, + private readonly _trashService: TrashService, private readonly _dossierStatsService: DossierStatsService, private readonly _formBuilder: FormBuilder, private readonly _dialogService: DossiersDialogService, @@ -137,7 +137,7 @@ export class EditDossierGeneralInfoComponent implements OnInit, EditDossierSecti }); this._dialogService.openDialog('confirm', null, data, async () => { this._loadingService.start(); - await firstValueFrom(this._trashDossiersService.delete(this.dossier)); + await firstValueFrom(this._trashService.deleteDossier(this.dossier)); this._editDossierDialogRef.close(); await this._router.navigate(['main', 'dossiers']); this._loadingService.stop(); diff --git a/apps/red-ui/src/app/modules/dossier/shared/shared-dossiers.module.ts b/apps/red-ui/src/app/modules/dossier/shared/shared-dossiers.module.ts index cdb1bbc83..57cb8d531 100644 --- a/apps/red-ui/src/app/modules/dossier/shared/shared-dossiers.module.ts +++ b/apps/red-ui/src/app/modules/dossier/shared/shared-dossiers.module.ts @@ -13,7 +13,6 @@ import { EditDossierDownloadPackageComponent } from '../dialogs/edit-dossier-dia import { EditDossierDictionaryComponent } from '../dialogs/edit-dossier-dialog/dictionary/edit-dossier-dictionary.component'; import { EditDossierAttributesComponent } from '../dialogs/edit-dossier-dialog/attributes/edit-dossier-attributes.component'; import { EditDossierTeamComponent } from '../dialogs/edit-dossier-dialog/edit-dossier-team/edit-dossier-team.component'; -import { EditDossierDeletedDocumentsComponent } from '../dialogs/edit-dossier-dialog/deleted-documents/edit-dossier-deleted-documents.component'; import { DateColumnComponent } from './components/date-column/date-column.component'; const components = [ @@ -23,7 +22,6 @@ const components = [ EditDossierDictionaryComponent, EditDossierAttributesComponent, EditDossierTeamComponent, - EditDossierDeletedDocumentsComponent, FileActionsComponent, DateColumnComponent, ]; diff --git a/apps/red-ui/src/app/modules/dossiers-listing/components/table-item/table-item.component.html b/apps/red-ui/src/app/modules/dossiers-listing/components/table-item/table-item.component.html index d84c0c53f..0ed998e63 100644 --- a/apps/red-ui/src/app/modules/dossiers-listing/components/table-item/table-item.component.html +++ b/apps/red-ui/src/app/modules/dossiers-listing/components/table-item/table-item.component.html @@ -1,6 +1,6 @@
- +
diff --git a/apps/red-ui/src/app/modules/shared/components/dossiers-listing-dossier-name/dossiers-listing-dossier-name.component.html b/apps/red-ui/src/app/modules/shared/components/dossier-name-column/dossier-name-column.component.html similarity index 86% rename from apps/red-ui/src/app/modules/shared/components/dossiers-listing-dossier-name/dossiers-listing-dossier-name.component.html rename to apps/red-ui/src/app/modules/shared/components/dossier-name-column/dossier-name-column.component.html index dea2c6bfa..978eb68c6 100644 --- a/apps/red-ui/src/app/modules/shared/components/dossiers-listing-dossier-name/dossiers-listing-dossier-name.component.html +++ b/apps/red-ui/src/app/modules/shared/components/dossier-name-column/dossier-name-column.component.html @@ -12,10 +12,10 @@
- {{ dossierStats.numberOfFiles }} + {{ dossier.isSoftDeleted ? dossierStats.numberOfSoftDeletedFiles : dossierStats.numberOfFiles }}
-
+
{{ dossierStats.numberOfPages }}
diff --git a/apps/red-ui/src/app/modules/shared/components/dossiers-listing-dossier-name/dossiers-listing-dossier-name.component.scss b/apps/red-ui/src/app/modules/shared/components/dossier-name-column/dossier-name-column.component.scss similarity index 100% rename from apps/red-ui/src/app/modules/shared/components/dossiers-listing-dossier-name/dossiers-listing-dossier-name.component.scss rename to apps/red-ui/src/app/modules/shared/components/dossier-name-column/dossier-name-column.component.scss diff --git a/apps/red-ui/src/app/modules/shared/components/dossiers-listing-dossier-name/dossiers-listing-dossier-name.component.ts b/apps/red-ui/src/app/modules/shared/components/dossier-name-column/dossier-name-column.component.ts similarity index 65% rename from apps/red-ui/src/app/modules/shared/components/dossiers-listing-dossier-name/dossiers-listing-dossier-name.component.ts rename to apps/red-ui/src/app/modules/shared/components/dossier-name-column/dossier-name-column.component.ts index fab9ad4de..710f5fdfc 100644 --- a/apps/red-ui/src/app/modules/shared/components/dossiers-listing-dossier-name/dossiers-listing-dossier-name.component.ts +++ b/apps/red-ui/src/app/modules/shared/components/dossier-name-column/dossier-name-column.component.ts @@ -2,17 +2,27 @@ import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; import { Dossier, DossierStats } from '@red/domain'; import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service'; import * as moment from 'moment'; +import { List } from '@iqser/common-ui'; const DUE_DATE_WARN_DAYS = 14; +interface PartialDossier extends Partial { + readonly dossierName: string; + readonly dossierTemplateId: string; + readonly dueDate?: string; + readonly date?: string; + readonly memberIds: List; + readonly isSoftDeleted: boolean; +} + @Component({ - selector: 'redaction-dossiers-listing-dossier-name', - templateUrl: './dossiers-listing-dossier-name.component.html', - styleUrls: ['./dossiers-listing-dossier-name.component.scss'], + selector: 'redaction-dossier-name-column', + templateUrl: './dossier-name-column.component.html', + styleUrls: ['./dossier-name-column.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class DossiersListingDossierNameComponent { - @Input() dossier: Dossier; +export class DossierNameColumnComponent { + @Input() dossier: PartialDossier; @Input() dossierStats: DossierStats; constructor(private readonly _dossierTemplatesService: DossierTemplatesService) {} diff --git a/apps/red-ui/src/app/modules/shared/components/file-name-column/file-name-column.component.html b/apps/red-ui/src/app/modules/shared/components/file-name-column/file-name-column.component.html new file mode 100644 index 000000000..f6b15ff1b --- /dev/null +++ b/apps/red-ui/src/app/modules/shared/components/file-name-column/file-name-column.component.html @@ -0,0 +1,21 @@ +
+
+ {{ file.filename }} +
+
+ +
+
+ + {{ primaryAttribute }} + +
+
+ + diff --git a/apps/red-ui/src/app/modules/shared/components/file-name-column/file-name-column.component.scss b/apps/red-ui/src/app/modules/shared/components/file-name-column/file-name-column.component.scss new file mode 100644 index 000000000..6d6ea372d --- /dev/null +++ b/apps/red-ui/src/app/modules/shared/components/file-name-column/file-name-column.component.scss @@ -0,0 +1,18 @@ +@use 'common-mixins'; + +.table-item-title { + max-width: 25vw; + + &.error { + color: var(--iqser-red-1); + } + + &.initial-processing { + color: var(--iqser-disabled); + } +} + +.primary-attribute { + padding-top: 6px; + @include common-mixins.line-clamp(1); +} diff --git a/apps/red-ui/src/app/modules/shared/components/file-name-column/file-name-column.component.ts b/apps/red-ui/src/app/modules/shared/components/file-name-column/file-name-column.component.ts new file mode 100644 index 000000000..c2e92358e --- /dev/null +++ b/apps/red-ui/src/app/modules/shared/components/file-name-column/file-name-column.component.ts @@ -0,0 +1,31 @@ +import { ChangeDetectionStrategy, Component, Input, OnChanges } from '@angular/core'; +import { PrimaryFileAttributeService } from '@services/primary-file-attribute.service'; +import { FileAttributes } from '@red/domain'; + +interface PartialFile { + readonly isError: boolean; + readonly isInitialProcessing: boolean; + readonly filename: string; + readonly numberOfPages: number; + readonly excludedPages: number[]; + readonly lastOCRTime?: string; + readonly fileAttributes: FileAttributes; +} + +@Component({ + selector: 'redaction-file-name-column', + templateUrl: './file-name-column.component.html', + styleUrls: ['./file-name-column.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class FileNameColumnComponent implements OnChanges { + @Input() file: PartialFile; + @Input() dossierTemplateId: string; + primaryAttribute: string; + + constructor(private readonly _primaryFileAttributeService: PrimaryFileAttributeService) {} + + ngOnChanges() { + this.primaryAttribute = this._primaryFileAttributeService.getPrimaryFileAttributeValue(this.file, this.dossierTemplateId); + } +} diff --git a/apps/red-ui/src/app/modules/shared/components/file-stats/file-stats.component.html b/apps/red-ui/src/app/modules/shared/components/file-stats/file-stats.component.html new file mode 100644 index 000000000..0078aa46a --- /dev/null +++ b/apps/red-ui/src/app/modules/shared/components/file-stats/file-stats.component.html @@ -0,0 +1,14 @@ +
+
+ + {{ file.numberOfPages }} +
+
+ + {{ file.excludedPages.length }} +
+
+ + {{ file.lastOCRTime | date: 'mediumDate' }} +
+
diff --git a/apps/red-ui/src/app/modules/shared/components/file-stats/file-stats.component.scss b/apps/red-ui/src/app/modules/shared/components/file-stats/file-stats.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/apps/red-ui/src/app/modules/shared/components/file-stats/file-stats.component.ts b/apps/red-ui/src/app/modules/shared/components/file-stats/file-stats.component.ts new file mode 100644 index 000000000..e2cd30c5d --- /dev/null +++ b/apps/red-ui/src/app/modules/shared/components/file-stats/file-stats.component.ts @@ -0,0 +1,11 @@ +import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; + +@Component({ + selector: 'redaction-file-stats', + templateUrl: './file-stats.component.html', + styleUrls: ['./file-stats.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class FileStatsComponent { + @Input() file: { numberOfPages: number; excludedPages: number[]; lastOCRTime?: string }; +} diff --git a/apps/red-ui/src/app/modules/shared/services/dictionary.service.ts b/apps/red-ui/src/app/modules/shared/services/dictionary.service.ts index 1ec02ea3b..740d8e21b 100644 --- a/apps/red-ui/src/app/modules/shared/services/dictionary.service.ts +++ b/apps/red-ui/src/app/modules/shared/services/dictionary.service.ts @@ -185,7 +185,7 @@ export class DictionaryService extends EntitiesService for (const dictionary of this._dictionariesMapService.get(dossierTemplateId)) { if (!dictionary.virtual && dictionary.addToDictionaryAction) { - possibleDictionaries.push(dictionary as Dictionary); + possibleDictionaries.push(dictionary); } } diff --git a/apps/red-ui/src/app/modules/shared/shared.module.ts b/apps/red-ui/src/app/modules/shared/shared.module.ts index 4305bfbd8..addd82cf7 100644 --- a/apps/red-ui/src/app/modules/shared/shared.module.ts +++ b/apps/red-ui/src/app/modules/shared/shared.module.ts @@ -28,7 +28,9 @@ import { EditorComponent } from './components/editor/editor.component'; import { ExpandableFileActionsComponent } from './components/expandable-file-actions/expandable-file-actions.component'; import { ProcessingIndicatorComponent } from '@shared/components/processing-indicator/processing-indicator.component'; import { DossierStateComponent } from '@shared/components/dossier-state/dossier-state.component'; -import { DossiersListingDossierNameComponent } from '@shared/components/dossiers-listing-dossier-name/dossiers-listing-dossier-name.component'; +import { FileStatsComponent } from './components/file-stats/file-stats.component'; +import { FileNameColumnComponent } from '@shared/components/file-name-column/file-name-column.component'; +import { DossierNameColumnComponent } from '@shared/components/dossier-name-column/dossier-name-column.component'; const buttons = [FileDownloadBtnComponent, UserButtonComponent]; @@ -46,7 +48,9 @@ const components = [ ExpandableFileActionsComponent, ProcessingIndicatorComponent, DossierStateComponent, - DossiersListingDossierNameComponent, + DossierNameColumnComponent, + FileStatsComponent, + FileNameColumnComponent, ...buttons, ]; diff --git a/apps/red-ui/src/app/services/entity-services/dossier-templates.service.ts b/apps/red-ui/src/app/services/entity-services/dossier-templates.service.ts index 471459d81..9547bfa4a 100644 --- a/apps/red-ui/src/app/services/entity-services/dossier-templates.service.ts +++ b/apps/red-ui/src/app/services/entity-services/dossier-templates.service.ts @@ -1,7 +1,7 @@ import { EntitiesService, List, mapEach, RequiredParam, Toaster, Validate } from '@iqser/common-ui'; import { DossierTemplate, IDossierTemplate } from '@red/domain'; import { Injectable, Injector } from '@angular/core'; -import { forkJoin, Observable, of } from 'rxjs'; +import { forkJoin, Observable, of, throwError } from 'rxjs'; import { FileAttributesService } from './file-attributes.service'; import { catchError, mapTo, switchMap, tap } from 'rxjs/operators'; import { DossierTemplateStatsService } from '@services/entity-services/dossier-template-stats.service'; diff --git a/apps/red-ui/src/app/services/entity-services/dossiers.service.provider.ts b/apps/red-ui/src/app/services/entity-services/dossiers.service.provider.ts index 0b176f0d7..0acf1c071 100644 --- a/apps/red-ui/src/app/services/entity-services/dossiers.service.provider.ts +++ b/apps/red-ui/src/app/services/entity-services/dossiers.service.provider.ts @@ -3,8 +3,11 @@ import { Injector, ProviderToken } from '@angular/core'; import { DossiersService } from '../dossiers/dossiers.service'; export const dossiersServiceResolver = (injector: Injector) => { - const route = injector.get(ActivatedRoute); - const token: ProviderToken = (route.firstChild || route).snapshot.data.dossiersService; + let route: ActivatedRoute = injector.get(ActivatedRoute); + while (route.firstChild) { + route = route.firstChild; + } + const token: ProviderToken = route.snapshot.data.dossiersService; return injector.get(token); }; diff --git a/apps/red-ui/src/app/services/entity-services/file-management.service.ts b/apps/red-ui/src/app/services/entity-services/file-management.service.ts index b900de21c..0d26bb4cd 100644 --- a/apps/red-ui/src/app/services/entity-services/file-management.service.ts +++ b/apps/red-ui/src/app/services/entity-services/file-management.service.ts @@ -26,21 +26,6 @@ export class FileManagementService extends GenericService { return super._post(fileIds, `delete/${dossierId}`).pipe(switchMap(() => this._filesService.loadAll(dossierId, routerPath))); } - @Validate() - hardDelete(@RequiredParam() dossierId: string, @RequiredParam() fileIds: List) { - const queryParams = fileIds.map(id => ({ key: 'fileIds', value: id })); - return super - .delete({}, `delete/hard-delete/${dossierId}`, queryParams) - .pipe(switchMap(() => this._dossierStatsService.getFor([dossierId]))); - } - - @Validate() - restore(@RequiredParam() files: List, @RequiredParam() dossierId: string) { - const fileIds = files.map(f => f.id); - const routerPath: string = files[0].routerPath; - return this._post(fileIds, `delete/restore/${dossierId}`).pipe(switchMap(() => this._filesService.loadAll(dossierId, routerPath))); - } - @Validate() rotatePage(@RequiredParam() body: IPageRotationRequest, @RequiredParam() dossierId: string, @RequiredParam() fileId: string) { return this._post(body, `rotate/${dossierId}/${fileId}`); diff --git a/apps/red-ui/src/app/services/entity-services/files.service.ts b/apps/red-ui/src/app/services/entity-services/files.service.ts index 7f58fadcd..06685575a 100644 --- a/apps/red-ui/src/app/services/entity-services/files.service.ts +++ b/apps/red-ui/src/app/services/entity-services/files.service.ts @@ -99,12 +99,4 @@ export class FilesService extends EntitiesService { switchMap(() => this.loadAll(dossierId, routerPath)), ); } - - /** - * Gets the deleted files for a dossier. - */ - @Validate() - getDeletedFilesFor(@RequiredParam() dossierId: string): Observable { - return this.getAll(`${this._defaultModelPath}/softdeleted/${dossierId}`); - } } diff --git a/apps/red-ui/src/app/services/entity-services/trash-dossiers.service.ts b/apps/red-ui/src/app/services/entity-services/trash-dossiers.service.ts deleted file mode 100644 index 158ff48d5..000000000 --- a/apps/red-ui/src/app/services/entity-services/trash-dossiers.service.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { Injectable, Injector } from '@angular/core'; -import { EntitiesService, mapEach, QueryParam, RequiredParam, Toaster, Validate } from '@iqser/common-ui'; -import { Dossier, IDossier, TrashDossier } from '@red/domain'; -import { catchError, switchMap, tap } from 'rxjs/operators'; -import { firstValueFrom, Observable, of } from 'rxjs'; -import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; -import * as moment from 'moment'; -import { ConfigService } from '../config.service'; -import { PermissionsService } from '../permissions.service'; -import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service'; - -export interface IDossiersStats { - totalPeople: number; - totalAnalyzedPages: number; -} - -@Injectable({ - providedIn: 'root', -}) -export class TrashDossiersService extends EntitiesService { - constructor( - protected readonly _injector: Injector, - private readonly _toaster: Toaster, - private readonly _configService: ConfigService, - private readonly _permissionsService: PermissionsService, - private readonly _activeDossiersService: ActiveDossiersService, - ) { - super(_injector, TrashDossier, 'dossier'); - } - - loadAll(): Observable { - return this.#getDeleted().pipe( - mapEach( - dossier => new TrashDossier(dossier, this.#getRestoreDate(dossier), this._permissionsService.canDeleteDossier(dossier)), - ), - tap(dossiers => this.setEntities(dossiers)), - ); - } - - delete(dossier: Dossier): Observable { - const showToast = () => { - this._toaster.error(_('dossier-listing.delete.delete-failed'), { params: dossier }); - return of({}); - }; - return super.delete(dossier.dossierId).pipe( - switchMap(() => this._activeDossiersService.loadAll()), - catchError(showToast), - ); - } - - @Validate() - restore(@RequiredParam() dossierIds: string[]): Promise { - return firstValueFrom( - this._post(dossierIds, 'deleted-dossiers/restore').pipe( - switchMap(() => this._activeDossiersService.loadAll()), - tap(() => this.#removeDossiers(dossierIds)), - ), - ); - } - - @Validate() - hardDelete(@RequiredParam() dossierIds: string[]): Promise { - const body = dossierIds.map(id => ({ key: 'dossierId', value: id })); - return firstValueFrom( - super.delete(body, 'deleted-dossiers/hard-delete', body).pipe( - switchMap(() => this._activeDossiersService.loadAll()), - tap(() => this.#removeDossiers(dossierIds)), - ), - ); - } - - #getRestoreDate(dossier: IDossier): string { - return moment(dossier.softDeletedTime).add(this._configService.values.DELETE_RETENTION_HOURS, 'hours').toISOString(); - } - - #getDeleted(): Observable { - return this.getAll('deleted-dossiers'); - } - - #removeDossiers(dossierIds: string[]): void { - this.setEntities(this.all.filter(dossier => !dossierIds.includes(dossier.id))); - } -} diff --git a/apps/red-ui/src/app/services/entity-services/trash.service.ts b/apps/red-ui/src/app/services/entity-services/trash.service.ts new file mode 100644 index 000000000..b8dc6ad17 --- /dev/null +++ b/apps/red-ui/src/app/services/entity-services/trash.service.ts @@ -0,0 +1,137 @@ +import { Injectable, Injector } from '@angular/core'; +import { GenericService, List, mapEach, QueryParam, RequiredParam, Toaster, Validate } from '@iqser/common-ui'; +import { Dossier, File, IDossier, IFile, TrashDossier, TrashFile, TrashItem } from '@red/domain'; +import { catchError, switchMap, take } from 'rxjs/operators'; +import { forkJoin, map, Observable, of } from 'rxjs'; +import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; +import { ConfigService } from '../config.service'; +import { PermissionsService } from '../permissions.service'; +import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service'; +import { UserService } from '@services/user.service'; +import { flatMap } from 'lodash'; +import { DossierStatsService } from '@services/dossiers/dossier-stats.service'; +import { FilesService } from '@services/entity-services/files.service'; + +export interface IDossiersStats { + totalPeople: number; + totalAnalyzedPages: number; +} + +@Injectable({ + providedIn: 'root', +}) +export class TrashService extends GenericService { + constructor( + protected readonly _injector: Injector, + private readonly _toaster: Toaster, + private readonly _configService: ConfigService, + private readonly _permissionsService: PermissionsService, + private readonly _activeDossiersService: ActiveDossiersService, + private readonly _userService: UserService, + private readonly _dossierStatsService: DossierStatsService, + private readonly _filesService: FilesService, + ) { + super(_injector, ''); + } + + deleteDossier(dossier: Dossier): Observable { + const showToast = () => { + this._toaster.error(_('dossier-listing.delete.delete-failed'), { params: dossier }); + return of({}); + }; + return this.delete(dossier.dossierId, 'dossier').pipe( + switchMap(() => this._activeDossiersService.loadAll()), + catchError(showToast), + ); + } + + @Validate() + restore(@RequiredParam() items: TrashItem[]): Observable { + return this.#executeAction( + items, + (dossierIds: string[]) => this._restoreDossiers(dossierIds), + (dossierId: string, fileIds: string[]) => this._restoreFiles(dossierId, fileIds), + ); + } + + @Validate() + hardDelete(@RequiredParam() items: TrashItem[]): Observable { + return this.#executeAction( + items, + (dossierIds: string[]) => this._hardDeleteDossiers(dossierIds), + (dossierId: string, fileIds: string[]) => this._hardDeleteFiles(dossierId, fileIds), + ); + } + + getDossiers(): Observable { + return this.getAll('deleted-dossiers').pipe( + mapEach( + dossier => + new TrashDossier( + dossier, + this._configService.values.DELETE_RETENTION_HOURS as number, + this._permissionsService.canDeleteDossier(dossier), + ), + ), + switchMap(dossiers => this._dossierStatsService.getFor(dossiers.map(d => d.id) as string[]).pipe(map(() => dossiers))), + ); + } + + getFiles(dossierIds = this._activeDossiersService.all.map(d => d.id)): Observable { + return this._post>(dossierIds, 'status/softdeleted').pipe( + map(res => flatMap(Object.values(res))), + mapEach(file => new File(file, this._userService.getNameForId(file.assignee), this._activeDossiersService.routerPath)), + mapEach(file => { + const dossierTemplateId = this._activeDossiersService.find(file.dossierId).dossierTemplateId; + return new TrashFile( + file, + dossierTemplateId, + this._configService.values.DELETE_RETENTION_HOURS as number, + this._permissionsService.canDeleteFile(file), + ); + }), + ); + } + + all(): Observable { + return forkJoin([this.getDossiers(), this.getFiles()]).pipe(map(result => flatMap(result))); + } + + #executeAction(items: TrashItem[], dossiersFn: (...args) => Observable, filesFn: (...args) => Observable) { + const requests$ = []; + + const dossierIds = items.filter(i => i.isDossier).map(i => i.id); + if (dossierIds.length) { + requests$.push(dossiersFn(dossierIds)); + } + + const files = items.filter(i => i.isFile) as TrashFile[]; + const dossierId: string = files.length ? files[0].dossierId : ''; + const fileIds = files.map(i => i.id); + if (files.length) { + requests$.push(filesFn(dossierId, fileIds)); + } + + return forkJoin(requests$.map(r => r.pipe(take(1)))); + } + + private _restoreDossiers(@RequiredParam() dossierIds: string[]): Observable { + return this._post(dossierIds, 'deleted-dossiers/restore').pipe(switchMap(() => this._activeDossiersService.loadAll())); + } + + private _hardDeleteDossiers(@RequiredParam() dossierIds: string[]): Observable { + const body = dossierIds.map(id => ({ key: 'dossierId', value: id })); + return this.delete(body, 'deleted-dossiers/hard-delete', body); + } + + private _hardDeleteFiles(@RequiredParam() dossierId: string, @RequiredParam() fileIds: List) { + const queryParams = fileIds.map(id => ({ key: 'fileIds', value: id })); + return super + .delete({}, `delete/hard-delete/${dossierId}`, queryParams) + .pipe(switchMap(() => this._dossierStatsService.getFor([dossierId]))); + } + + private _restoreFiles(@RequiredParam() dossierId: string, @RequiredParam() fileIds: List) { + return this._post(fileIds, `delete/restore/${dossierId}`).pipe(switchMap(() => this._filesService.loadAll(dossierId, 'dossiers'))); + } +} diff --git a/apps/red-ui/src/app/services/primary-file-attribute.service.ts b/apps/red-ui/src/app/services/primary-file-attribute.service.ts index 5a8acdc18..c0f334af0 100644 --- a/apps/red-ui/src/app/services/primary-file-attribute.service.ts +++ b/apps/red-ui/src/app/services/primary-file-attribute.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@angular/core'; import { FileAttributesService } from './entity-services/file-attributes.service'; -import { IFile } from '@red/domain'; +import { FileAttributes } from '@red/domain'; @Injectable({ providedIn: 'root', @@ -8,7 +8,7 @@ import { IFile } from '@red/domain'; export class PrimaryFileAttributeService { constructor(private readonly _fileAttributesService: FileAttributesService) {} - getPrimaryFileAttributeValue(file: IFile, dossierTemplateId: string) { + getPrimaryFileAttributeValue(file: { fileAttributes: FileAttributes }, dossierTemplateId: string) { const fileAttributesConfig = this._fileAttributesService.getFileAttributeConfig(dossierTemplateId); let primaryAttribute; diff --git a/apps/red-ui/src/assets/i18n/de.json b/apps/red-ui/src/assets/i18n/de.json index 83b76d6e1..9e1394b31 100644 --- a/apps/red-ui/src/assets/i18n/de.json +++ b/apps/red-ui/src/assets/i18n/de.json @@ -517,17 +517,15 @@ "question": "Möchten Sie fortfahren?", "title": "Dokument löschen" }, + "delete-items": { + "question": "", + "title": "" + }, "delete-justification": { "question": "Möchten Sie {count, plural, one{diese Begründung} other{diese Begründung}} wirklich löschen?", "title": "{count, plural, one{{justificationName}} other{ausgewählte Begründungen}} löschen" }, "input-label": "Bitte geben Sie unten Folgendes ein, um fortzufahren", - "permanently-delete-file": { - "confirmation-text": "{filesCount, plural, one{Document} other{Documents}} löschen", - "deny-text": "{filesCount, plural, one{Dokument} other{Dokumente}} behalten", - "question": "Möchten Sie {filesCount, plural, one{dieses Dokument} other{diese Dokumente}} wirklich löschen?", - "title": "{filesCount, plural, one{{fileName}} other{ausgewählte Dokumente}} löschen" - }, "report-template-same-name": { "confirmation-text": "Ja. Hochladen fortsetzen", "deny-text": "Nein. Hochladen abbrechen", @@ -1026,31 +1024,6 @@ }, "change-successful": "Dossier wurde aktualisiert.", "delete-successful": "Dossier wurde gelöscht.", - "deleted-documents": { - "action": { - "delete": "Endgültig löschen", - "restore": "Wiederherstellen" - }, - "bulk": { - "delete": "Ausgewählte Dokumente endgültig löschen", - "restore": "Ausgewählte Dokumente wiederherstellen" - }, - "instructions": "Gelöschte Objekte können bis zu {hours} Stunden nach ihrer Löschung wiederhergestellt werden", - "no-data": { - "title": "Es sind keine gelöschten Dokumente vorhanden." - }, - "table-col-names": { - "assignee": "Bevollmächtigter", - "deleted-on": "Gelöscht am", - "name": "Name", - "pages": "Seiten", - "status": "Status", - "time-to-restore": "Verbleibende Zeit für Wiederherstellung" - }, - "table-header": { - "label": "{length} {length, plural, one{gelöschtes Dokument} other{gelöschte Dokumente}}" - } - }, "dictionary": { "display-name": { "cancel": "Abbrechen", @@ -1087,7 +1060,6 @@ "missing-owner": "", "nav-items": { "choose-download": "Wählen Sie die Dokumente für Ihr Download-Paket aus:", - "deleted-documents": "Gelöschte Dokumente", "dictionary": "Wörterbuch", "dossier-attributes": "Dossier-Attribute", "dossier-dictionary": "Dossier-Wörterbuch", @@ -1851,6 +1823,7 @@ }, "table-col-names": { "deleted-on": "Gelöscht am", + "dossier": "", "name": "Name", "owner": "Eigentümer", "time-to-restore": "Verbleibende Zeit für Wiederherstellung" diff --git a/apps/red-ui/src/assets/i18n/en.json b/apps/red-ui/src/assets/i18n/en.json index 36ed8b809..4787e37d3 100644 --- a/apps/red-ui/src/assets/i18n/en.json +++ b/apps/red-ui/src/assets/i18n/en.json @@ -517,17 +517,15 @@ "question": "Do you wish to proceed?", "title": "Delete Document" }, + "delete-items": { + "question": "Are you sure you want to delete {itemsCount, plural, one{this item} other{these items}}?", + "title": "Delete {itemsCount, plural, one{{name}} other{Selected Items}}" + }, "delete-justification": { "question": "Are you sure you want to delete {count, plural, one{this justification} other{these justifications}}?", "title": "Delete {count, plural, one{{justificationName}} other{Selected Justifications}}" }, "input-label": "To proceed please type below", - "permanently-delete-file": { - "confirmation-text": "Delete {filesCount, plural, one{Document} other{Documents}}", - "deny-text": "Keep {filesCount, plural, one{Document} other{Documents}}", - "question": "Are you sure you want to delete {filesCount, plural, one{this document} other{these documents}}?", - "title": "Delete {filesCount, plural, one{{fileName}} other{Selected Documents}}" - }, "report-template-same-name": { "confirmation-text": "Yes. Continue upload", "deny-text": "No. Cancel Upload", @@ -1026,31 +1024,6 @@ }, "change-successful": "Dossier {dossierName} was updated.", "delete-successful": "Dossier {dossierName} was deleted.", - "deleted-documents": { - "action": { - "delete": "Delete Forever", - "restore": "Restore" - }, - "bulk": { - "delete": "Forever Delete Selected Documents", - "restore": "Restore Selected Documents" - }, - "instructions": "Deleted items can be restored up to {hours} hours from their deletion", - "no-data": { - "title": "There are no deleted documents." - }, - "table-col-names": { - "assignee": "Assignee", - "deleted-on": "Deleted On", - "name": "Name", - "pages": "Pages", - "status": "Status", - "time-to-restore": "Time To Restore" - }, - "table-header": { - "label": "{length} deleted {length, plural, one{document} other{documents}}" - } - }, "dictionary": { "display-name": { "cancel": "Cancel", @@ -1087,7 +1060,6 @@ "missing-owner": "You cannot edit the dossier because the owner is missing!", "nav-items": { "choose-download": "Choose what is included at download:", - "deleted-documents": "Deleted Documents", "dictionary": "Dictionary", "dossier-attributes": "Dossier Attributes", "dossier-dictionary": "Dossier Dictionary", @@ -1839,24 +1811,25 @@ "restore": "Restore" }, "bulk": { - "delete": "Forever Delete Selected Dossiers", - "restore": "Restore Selected Dossiers" + "delete": "Forever Delete Selected Items", + "restore": "Restore Selected Items" }, "label": "Trash", "no-data": { - "title": "There are no dossiers yet." + "title": "There are no deleted items yet." }, "no-match": { - "title": "No dossiers match your current filters." + "title": "No items match your current filters." }, "table-col-names": { "deleted-on": "Deleted on", + "dossier": "Dossier", "name": "Name", - "owner": "Owner", + "owner": "Owner/assignee", "time-to-restore": "Time to restore" }, "table-header": { - "title": "{length} deleted {length, plural, one{dossier} other{dossiers}}" + "title": "{length} deleted {length, plural, one{item} other{items}}" } }, "type": "Type", diff --git a/libs/common-ui b/libs/common-ui index a6c4093d3..8a992aa44 160000 --- a/libs/common-ui +++ b/libs/common-ui @@ -1 +1 @@ -Subproject commit a6c4093d3553c4d0f95e496678e2a8f1cab0b9de +Subproject commit 8a992aa440ff24d1244e24edea3ce75fdadbebd5 diff --git a/libs/red-domain/src/index.ts b/libs/red-domain/src/index.ts index e1b7070bc..99077ba8e 100644 --- a/libs/red-domain/src/index.ts +++ b/libs/red-domain/src/index.ts @@ -21,5 +21,5 @@ export * from './lib/signature'; export * from './lib/legal-basis'; export * from './lib/dossier-stats'; export * from './lib/dossier-state'; -export * from './lib/trash-dossier'; +export * from './lib/trash'; export * from './lib/text-highlight'; diff --git a/libs/red-domain/src/lib/dossier-stats/dossier-stats.model.ts b/libs/red-domain/src/lib/dossier-stats/dossier-stats.model.ts index 7460d50b9..142f9af37 100644 --- a/libs/red-domain/src/lib/dossier-stats/dossier-stats.model.ts +++ b/libs/red-domain/src/lib/dossier-stats/dossier-stats.model.ts @@ -43,6 +43,7 @@ export class DossierStats implements IDossierStats { readonly numberOfFiles: number; readonly numberOfProcessingFiles: number; readonly processingStats: ProcessingStats; + readonly numberOfSoftDeletedFiles: number; readonly hasFiles: boolean; @@ -58,6 +59,7 @@ export class DossierStats implements IDossierStats { this.hasUpdatesFilePresent = stats.hasUpdatesFilePresent; this.numberOfPages = stats.numberOfPages; this.numberOfFiles = stats.numberOfFiles; + this.numberOfSoftDeletedFiles = stats.numberOfSoftDeletedFiles; this.numberOfProcessingFiles = Object.entries(this.fileCountPerProcessingStatus) .filter(([key]) => isProcessingStatuses.includes(key as ProcessingFileStatus)) .reduce((count, [, value]) => count + value, 0); diff --git a/libs/red-domain/src/lib/dossier-stats/dossier-stats.ts b/libs/red-domain/src/lib/dossier-stats/dossier-stats.ts index 09a07870a..ae80a4921 100644 --- a/libs/red-domain/src/lib/dossier-stats/dossier-stats.ts +++ b/libs/red-domain/src/lib/dossier-stats/dossier-stats.ts @@ -12,4 +12,5 @@ export interface IDossierStats { hasUpdatesFilePresent: boolean; numberOfPages: number; numberOfFiles: number; + numberOfSoftDeletedFiles: number; } diff --git a/libs/red-domain/src/lib/dossiers/dossier.model.ts b/libs/red-domain/src/lib/dossiers/dossier.model.ts index fba166c8a..b0e04911a 100644 --- a/libs/red-domain/src/lib/dossiers/dossier.model.ts +++ b/libs/red-domain/src/lib/dossiers/dossier.model.ts @@ -64,6 +64,10 @@ export class Dossier implements IDossier, IListable, IRouterPath { return this.status === DossierStatuses.ACTIVE; } + get isSoftDeleted(): boolean { + return this.status === DossierStatuses.DELETED; + } + hasMember(memberId: string): boolean { return !!this.memberIds && this.memberIds.indexOf(memberId) >= 0; } diff --git a/libs/red-domain/src/lib/files/file.model.ts b/libs/red-domain/src/lib/files/file.model.ts index 60b2911f1..df6ac4929 100644 --- a/libs/red-domain/src/lib/files/file.model.ts +++ b/libs/red-domain/src/lib/files/file.model.ts @@ -16,7 +16,7 @@ export class File extends Entity implements IFile, IRouterPath { readonly dossierId: string; readonly excluded: boolean; readonly excludedFromAutomaticAnalysis: boolean; - readonly fileAttributes?: FileAttributes; + readonly fileAttributes: FileAttributes; readonly fileId: string; readonly filename: string; readonly hasAnnotationComments: boolean; @@ -25,6 +25,7 @@ export class File extends Entity implements IFile, IRouterPath { readonly hasRedactions: boolean; readonly hasUpdates: boolean; readonly lastOCRTime?: string; + readonly softDeletedTime?: string; readonly lastProcessed?: string; readonly lastReviewer?: string; readonly lastApprover?: string; @@ -32,7 +33,7 @@ export class File extends Entity implements IFile, IRouterPath { readonly lastUploaded?: string; readonly legalBasisVersion?: number; readonly numberOfAnalyses: number; - readonly numberOfPages?: number; + readonly numberOfPages: number; readonly rulesVersion?: number; readonly uploader?: string; readonly excludedPages: number[]; @@ -72,7 +73,6 @@ export class File extends Entity implements IFile, IRouterPath { this.dossierId = file.dossierId; this.excluded = !!file.excluded; this.excludedFromAutomaticAnalysis = !!file.excludedFromAutomaticAnalysis; - this.fileAttributes = file.fileAttributes; this.fileId = file.fileId; this.filename = file.filename; this.hasAnnotationComments = !!file.hasAnnotationComments; @@ -81,6 +81,7 @@ export class File extends Entity implements IFile, IRouterPath { this.hasRedactions = !!file.hasRedactions; this.hasUpdates = !!file.hasUpdates; this.lastOCRTime = file.lastOCRTime; + this.softDeletedTime = file.softDeletedTime; this.lastProcessed = file.lastProcessed; this.lastReviewer = file.lastReviewer; this.lastApprover = file.lastApprover; @@ -115,9 +116,8 @@ export class File extends Entity implements IFile, IRouterPath { this.canBeOpened = !this.isError && !this.isUnprocessed && this.numberOfAnalyses > 0; this.canBeOCRed = !this.excluded && !this.lastOCRTime && (this.isNew || this.isUnderReview || this.isUnderApproval); - if (!this.fileAttributes || !this.fileAttributes.attributeIdToValue) { - this.fileAttributes = { attributeIdToValue: {} }; - } + this.fileAttributes = + file.fileAttributes && file.fileAttributes.attributeIdToValue ? file.fileAttributes : { attributeIdToValue: {} }; } get id(): string { diff --git a/libs/red-domain/src/lib/files/file.ts b/libs/red-domain/src/lib/files/file.ts index 26dac2ec5..99426dcd7 100644 --- a/libs/red-domain/src/lib/files/file.ts +++ b/libs/red-domain/src/lib/files/file.ts @@ -137,7 +137,7 @@ export interface IFile { /** * Shows if the file is soft deleted. */ - readonly softDeleted?: string; + readonly softDeletedTime?: string; /** * The ID of the user who uploaded the file. */ diff --git a/libs/red-domain/src/lib/trash-dossier/index.ts b/libs/red-domain/src/lib/trash-dossier/index.ts deleted file mode 100644 index a90f3227f..000000000 --- a/libs/red-domain/src/lib/trash-dossier/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './trash-dossier.model'; diff --git a/libs/red-domain/src/lib/trash-dossier/trash-dossier.model.ts b/libs/red-domain/src/lib/trash-dossier/trash-dossier.model.ts deleted file mode 100644 index aca8adf9d..000000000 --- a/libs/red-domain/src/lib/trash-dossier/trash-dossier.model.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { getLeftDateTime, List } from '@iqser/common-ui'; -import { DownloadFileType } from '../shared'; -import { DossierStatus, IDossier } from '../dossiers'; - -export class TrashDossier implements IDossier { - readonly dossierId: string; - readonly dossierTemplateId: string; - readonly ownerId: string; - readonly memberIds: List; - readonly approverIds: List; - readonly reportTemplateIds: List; - readonly dossierName: string; - readonly dossierStatusId: string; - readonly date: string; - readonly dueDate?: string; - readonly description?: string; - readonly downloadFileTypes?: List; - readonly hardDeletedTime?: string; - readonly softDeletedTime?: string; - readonly startDate?: string; - readonly status: DossierStatus; - readonly watermarkEnabled: boolean; - readonly watermarkPreviewEnabled: boolean; - readonly archivedTime: string; - readonly hasReviewers: boolean; - readonly canRestore: boolean; - - constructor(dossier: IDossier, readonly restoreDate: string, readonly canHardDelete: boolean) { - this.dossierId = dossier.dossierId; - this.approverIds = dossier.approverIds; - this.date = dossier.date; - this.description = dossier.description; - this.dossierName = dossier.dossierName; - this.dossierStatusId = dossier.dossierStatusId; - this.dossierTemplateId = dossier.dossierTemplateId; - this.downloadFileTypes = dossier.downloadFileTypes; - this.dueDate = dossier.dueDate; - this.hardDeletedTime = dossier.hardDeletedTime; - this.memberIds = dossier.memberIds; - this.ownerId = dossier.ownerId; - this.reportTemplateIds = dossier.reportTemplateIds; - this.softDeletedTime = dossier.softDeletedTime; - this.startDate = dossier.startDate; - this.status = dossier.status; - this.watermarkEnabled = dossier.watermarkEnabled; - this.watermarkPreviewEnabled = dossier.watermarkPreviewEnabled; - this.archivedTime = dossier.archivedTime; - this.hasReviewers = !!this.memberIds && this.memberIds.length > 1; - - this.canRestore = this.#canRestoreDossier(restoreDate); - // Because of migrations, for some this is not set - this.softDeletedTime = dossier.softDeletedTime || '-'; - } - - get id(): string { - return this.dossierId; - } - - get searchKey(): string { - return this.dossierName; - } - - #canRestoreDossier(restoreDate: string): boolean { - const { daysLeft, hoursLeft, minutesLeft } = getLeftDateTime(restoreDate); - - return daysLeft >= 0 && hoursLeft >= 0 && minutesLeft > 0; - } -} diff --git a/libs/red-domain/src/lib/trash/index.ts b/libs/red-domain/src/lib/trash/index.ts new file mode 100644 index 000000000..a0d146cc8 --- /dev/null +++ b/libs/red-domain/src/lib/trash/index.ts @@ -0,0 +1,3 @@ +export * from './trash.item'; +export * from './trash-dossier.model'; +export * from './trash-file.model'; diff --git a/libs/red-domain/src/lib/trash/trash-dossier.model.ts b/libs/red-domain/src/lib/trash/trash-dossier.model.ts new file mode 100644 index 000000000..7cd317f11 --- /dev/null +++ b/libs/red-domain/src/lib/trash/trash-dossier.model.ts @@ -0,0 +1,45 @@ +import { List } from '@iqser/common-ui'; +import { IDossier } from '../dossiers'; +import { TrashItem } from './trash.item'; + +export class TrashDossier extends TrashItem { + readonly type = 'dossier'; + readonly icon = 'red:folder'; + + readonly isSoftDeleted = true; + + readonly dossierId: string; + readonly dossierTemplateId: string; + readonly dossierName: string; + readonly memberIds: List; + readonly date: string; + readonly dueDate?: string; + readonly ownerId: string; + readonly softDeletedTime: string; + + constructor(dossier: IDossier, protected readonly _retentionHours: number, readonly canHardDelete: boolean) { + super(_retentionHours, dossier.softDeletedTime, canHardDelete); + this.dossierId = dossier.dossierId; + this.dossierTemplateId = dossier.dossierTemplateId; + this.date = dossier.date; + this.dossierName = dossier.dossierName; + this.dueDate = dossier.dueDate; + this.memberIds = dossier.memberIds; + this.ownerId = dossier.ownerId; + + // Because of migrations, for some this is not set + this.softDeletedTime = dossier.softDeletedTime || '-'; + } + + get id(): string { + return this.dossierId; + } + + get searchKey(): string { + return this.dossierName; + } + + get name(): string { + return this.dossierName; + } +} diff --git a/libs/red-domain/src/lib/trash/trash-file.model.ts b/libs/red-domain/src/lib/trash/trash-file.model.ts new file mode 100644 index 000000000..9e1d3b8d2 --- /dev/null +++ b/libs/red-domain/src/lib/trash/trash-file.model.ts @@ -0,0 +1,56 @@ +import { TrashItem } from './trash.item'; +import { File, IFile } from '../files'; +import { FileAttributes } from '../file-attributes'; + +export class TrashFile extends TrashItem implements Partial { + readonly type = 'file'; + readonly icon = 'iqser:document'; + + readonly dossierId: string; + readonly fileId: string; + readonly filename: string; + readonly assignee?: string; + + readonly numberOfPages: number; + readonly excludedPages: number[]; + readonly lastOCRTime?: string; + readonly fileAttributes: FileAttributes; + + readonly isError: boolean; + readonly isInitialProcessing: boolean; + + constructor( + file: File, + readonly dossierTemplateId: string, + protected readonly _retentionHours: number, + readonly canHardDelete: boolean, + ) { + super(_retentionHours, file.softDeletedTime, canHardDelete); + this.fileId = file.fileId; + this.dossierId = file.dossierId; + this.filename = file.filename; + this.assignee = file.assignee; + this.numberOfPages = file.numberOfPages || 0; + this.excludedPages = file.excludedPages || []; + this.lastOCRTime = file.lastOCRTime; + this.fileAttributes = file.fileAttributes; + this.isError = file.isError; + this.isInitialProcessing = file.isInitialProcessing; + } + + get id(): string { + return this.fileId; + } + + get searchKey(): string { + return this.filename; + } + + get name(): string { + return this.filename; + } + + get ownerId(): string | undefined { + return this.assignee; + } +} diff --git a/libs/red-domain/src/lib/trash/trash.item.ts b/libs/red-domain/src/lib/trash/trash.item.ts new file mode 100644 index 000000000..3676937f1 --- /dev/null +++ b/libs/red-domain/src/lib/trash/trash.item.ts @@ -0,0 +1,43 @@ +import { getLeftDateTime } from '@iqser/common-ui'; +import * as moment from 'moment'; + +export abstract class TrashItem { + abstract readonly type: 'dossier' | 'file'; + abstract readonly ownerId?: string; + abstract readonly dossierId: string; + abstract readonly icon: string; + readonly canRestore: boolean; + readonly restoreDate: string; + + protected constructor( + protected readonly _retentionHours: number, + readonly softDeletedTime: string | undefined, + readonly canHardDelete: boolean, + ) { + this.restoreDate = this.#restoreDate; + this.canRestore = this.#canRestore; + } + + abstract get id(): string; + + abstract get searchKey(): string; + + abstract get name(): string; + + get isDossier(): boolean { + return this.type === 'dossier'; + } + + get isFile(): boolean { + return this.type === 'file'; + } + + get #canRestore(): boolean { + const { daysLeft, hoursLeft, minutesLeft } = getLeftDateTime(this.restoreDate); + return daysLeft >= 0 && hoursLeft >= 0 && minutesLeft > 0; + } + + get #restoreDate(): string { + return moment(this.softDeletedTime).add(this._retentionHours, 'hours').toISOString(); + } +}