diff --git a/apps/red-ui/src/app/modules/search/search-screen/search-screen.component.html b/apps/red-ui/src/app/modules/search/search-screen/search-screen.component.html
index 89d1de2b1..3d80ba881 100644
--- a/apps/red-ui/src/app/modules/search/search-screen/search-screen.component.html
+++ b/apps/red-ui/src/app/modules/search/search-screen/search-screen.component.html
@@ -89,3 +89,5 @@
+
+
diff --git a/apps/red-ui/src/app/modules/search/search-screen/search-screen.component.ts b/apps/red-ui/src/app/modules/search/search-screen/search-screen.component.ts
index 5eefcaee8..22caab14b 100644
--- a/apps/red-ui/src/app/modules/search/search-screen/search-screen.component.ts
+++ b/apps/red-ui/src/app/modules/search/search-screen/search-screen.component.ts
@@ -1,4 +1,4 @@
-import { ChangeDetectionStrategy, Component, forwardRef, Injector, OnDestroy } from '@angular/core';
+import { ChangeDetectionStrategy, Component, forwardRef, Injector, OnDestroy, OnInit } from '@angular/core';
import {
DefaultListingServices,
IFilterGroup,
@@ -10,18 +10,29 @@ import {
SortingOrders,
TableColumnConfig,
} from '@iqser/common-ui';
-import { combineLatest, Observable, of } from 'rxjs';
+import { combineLatest, firstValueFrom, Observable, of } from 'rxjs';
import { debounceTime, map, startWith, switchMap, tap } from 'rxjs/operators';
import { ActivatedRoute, Router } from '@angular/router';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { workflowFileStatusTranslations } from '@translations/file-status-translations';
import { TranslateService } from '@ngx-translate/core';
import { RouterHistoryService } from '@services/router-history.service';
-import { Dossier, DOSSIERS_ARCHIVE, IMatchedDocument, ISearchListItem, ISearchResponse } from '@red/domain';
+import {
+ Dossier,
+ DOSSIERS_ARCHIVE,
+ DossierTemplate,
+ IMatchedDocument,
+ ISearchListItem,
+ ISearchResponse,
+ WorkflowFileStatus,
+ WorkflowFileStatuses,
+} from '@red/domain';
import { FilesMapService } from '@services/files/files-map.service';
import { PlatformSearchService } from '@services/entity-services/platform-search.service';
import { FeaturesService } from '@services/features.service';
import { DossiersCacheService } from '../../../services/dossiers/dossiers-cache.service';
+import { DossierTemplatesService } from '../../../services/dossier-templates/dossier-templates.service';
+import { UserService } from '../../../services/user.service';
@Component({
templateUrl: './search-screen.component.html',
@@ -44,11 +55,16 @@ export class SearchScreenComponent extends ListingComponent imp
readonly searchResults$ = combineLatest([this._queryChanged, this._filtersChanged$]).pipe(
tap(() => this._loadingService.start()),
- tap(([query, [dossierIds, onlyActive]]) => this._updateNavigation(query, dossierIds, onlyActive)),
- switchMap(([query, [dossierIds, onlyActive]]) =>
+ tap(([query, [dossierIds, workflowStatus, assignee, dossierTemplateIds, onlyActive]]) =>
+ this._updateNavigation(query, dossierIds, workflowStatus, assignee, dossierTemplateIds, onlyActive),
+ ),
+ switchMap(([query, [dossierIds, workflowStatus, assignee, dossierTemplateIds, onlyActive]]) =>
this._platformSearchService.search({
query,
dossierIds,
+ dossierTemplateIds,
+ workflowStatus,
+ assignee,
includeDeletedDossiers: false,
includeArchivedDossiers: !onlyActive,
}),
@@ -59,6 +75,8 @@ export class SearchScreenComponent extends ListingComponent imp
tap(() => this._loadingService.stop()),
);
+ readonly dossierTemplates$ = this._dossierTemplateService.loadAll().pipe(tap(templates => this._addTemplateFilter(templates)));
+
constructor(
private readonly _router: Router,
protected readonly _injector: Injector,
@@ -70,11 +88,16 @@ export class SearchScreenComponent extends ListingComponent imp
private readonly _filesMapService: FilesMapService,
private readonly _platformSearchService: PlatformSearchService,
private readonly _featuresService: FeaturesService,
+ private readonly _dossierTemplateService: DossierTemplatesService,
+ private readonly _userService: UserService,
) {
super(_injector);
this.searchService.skip = true;
this.sortingService.setSortingOption({ column: 'searchKey', order: SortingOrders.desc });
+ this._initFilters();
+ }
+ private _initFilters() {
const dossierIds = this._routeDossierIds;
const dossierToFilter = ({ dossierName, id }: Dossier) => {
const checked = dossierIds.includes(id);
@@ -88,17 +111,86 @@ export class SearchScreenComponent extends ListingComponent imp
filters: this._dossiersCacheService.all.map(dossierToFilter),
checker: keyChecker('dossierId'),
};
- this.filterService.addFilterGroups([dossierNameFilter]);
+
+ const status = this._routeStatus;
+ const statusToFilter = (workflowStatus: WorkflowFileStatus) => {
+ const checked = status === workflowStatus;
+ return new NestedFilter({
+ id: workflowStatus,
+ label: this._translateService.instant(workflowFileStatusTranslations[workflowStatus]),
+ checked,
+ });
+ };
+ const workflowStatusFilter: IFilterGroup = {
+ slug: 'status',
+ label: this._translateService.instant('search-screen.filters.status'),
+ icon: 'red:status',
+ filters: Object.values(WorkflowFileStatuses).map(statusToFilter),
+ singleSelect: true,
+ checker: keyChecker('status'),
+ };
+
+ const assignee = this._routeAssignee;
+ const assigneeToFilter = (userId: string) => {
+ const checked = assignee === userId;
+ return new NestedFilter({ id: userId, label: this._userService.getNameForId(userId), checked });
+ };
+ const assigneeFilter: IFilterGroup = {
+ slug: 'assignee',
+ label: this._translateService.instant('search-screen.filters.assignee'),
+ icon: 'red:user',
+ filters: this._userService.all.map(user => user.id).map(assigneeToFilter),
+ singleSelect: true,
+ checker: keyChecker('assignee'),
+ };
+
+ assigneeFilter.filters.push(
+ new NestedFilter({
+ id: null,
+ label: this._translateService.instant('initials-avatar.unassigned'),
+ }),
+ );
+
+ this.filterService.addFilterGroups([dossierNameFilter, workflowStatusFilter, assigneeFilter]);
const onlyActiveLabel = this._translateService.instant('search-screen.filters.only-active');
if (this.#enabledArchive) {
this.filterService.addSingleFilter({ id: 'onlyActiveDossiers', label: onlyActiveLabel, checked: this._routeOnlyActive });
}
}
+ private _addTemplateFilter(templates: DossierTemplate[]) {
+ const templatesIds = this._routeDossierTemplateIds;
+ const templateToFilter = ({ name, id }: DossierTemplate) => {
+ const checked = templatesIds?.includes(id);
+ return new NestedFilter({ id, label: name, checked });
+ };
+ const templateNameFilter: IFilterGroup = {
+ slug: 'templates',
+ label: this._translateService.instant('search-screen.filters.by-template'),
+ filterceptionPlaceholder: this._translateService.instant('search-screen.filters.search-by-template-placeholder'),
+ icon: 'red:template',
+ filters: templates.map(templateToFilter),
+ checker: keyChecker('dossierTemplateId'),
+ };
+ this.filterService.addFilterGroups([templateNameFilter]);
+ }
+
private get _routeDossierIds(): string[] {
return this._activatedRoute.snapshot.queryParamMap.get('dossierIds').split(',');
}
+ private get _routeDossierTemplateIds(): string[] {
+ return this._activatedRoute.snapshot.queryParamMap.get('dossierTemplateIds')?.split(',');
+ }
+
+ private get _routeStatus(): WorkflowFileStatus {
+ return this._activatedRoute.snapshot.queryParamMap.get('status') as WorkflowFileStatus;
+ }
+
+ private get _routeAssignee(): string {
+ return this._activatedRoute.snapshot.queryParamMap.get('assignee');
+ }
+
private get _routeOnlyActive(): boolean {
return this._activatedRoute.snapshot.queryParamMap.get('onlyActive') === 'true';
}
@@ -119,7 +211,7 @@ export class SearchScreenComponent extends ListingComponent imp
return this._featuresService.isEnabled(DOSSIERS_ARCHIVE);
}
- private get _filtersChanged$(): Observable<[string[], boolean]> {
+ private get _filtersChanged$(): Observable<[string[], WorkflowFileStatus, string, string[], boolean]> {
const onlyActiveDossiers$ = this.#enabledArchive
? this.filterService.getSingleFilter('onlyActiveDossiers').pipe(map(f => !!f.checked))
: of(true);
@@ -127,9 +219,18 @@ export class SearchScreenComponent extends ListingComponent imp
return combineLatest([filterGroups$, onlyActiveDossiers$]).pipe(
map(([groups, onlyActive]) => {
const dossierIds: string[] = groups[0].filters.filter(v => v.checked).map(v => v.id);
- return [dossierIds, onlyActive];
+ const workflowStatus: WorkflowFileStatus = groups[1].filters.filter(v => v.checked).map(v => v.id)[0] as WorkflowFileStatus;
+ const assignee: string = groups[2].filters.filter(v => v.checked).map(v => v.id)[0];
+ const dossierTemplateIds: string[] = groups[3]?.filters.filter(v => v.checked).map(v => v.id);
+ return [dossierIds, workflowStatus, assignee, dossierTemplateIds, onlyActive];
}),
- startWith<[string[], boolean]>([this._routeDossierIds, this._routeOnlyActive]),
+ startWith<[string[], WorkflowFileStatus, string, string[], boolean]>([
+ this._routeDossierIds,
+ this._routeStatus,
+ this._routeAssignee,
+ this._routeDossierTemplateIds,
+ this._routeOnlyActive,
+ ]),
);
}
@@ -138,8 +239,22 @@ export class SearchScreenComponent extends ListingComponent imp
this.searchService.searchValue = newQuery ?? '';
}
- private _updateNavigation(query?: string, dossierIds?: string[], onlyActive?: boolean): Promise {
- const queryParams = { query, dossierIds: dossierIds.join(','), onlyActive };
+ private _updateNavigation(
+ query?: string,
+ dossierIds?: string[],
+ workflowStatus?: WorkflowFileStatus,
+ assignee?: string,
+ dossierTemplateIds?: string[],
+ onlyActive?: boolean,
+ ): Promise {
+ const queryParams = {
+ query,
+ dossierIds: dossierIds.join(','),
+ dossierTemplateIds: dossierTemplateIds?.join(','),
+ workflowStatus,
+ assignee,
+ onlyActive,
+ };
return this._router.navigate([], { queryParams, replaceUrl: true });
}
@@ -171,6 +286,7 @@ export class SearchScreenComponent extends ListingComponent imp
return {
id: fileId,
dossierId,
+ dossierTemplateId: file.dossierTemplateId,
deleted: dossierDeleted,
archived: dossierArchived,
unmatched: unmatchedTerms || null,
diff --git a/apps/red-ui/src/app/services/entity-services/platform-search.service.ts b/apps/red-ui/src/app/services/entity-services/platform-search.service.ts
index 7f51abf7c..6e8398581 100644
--- a/apps/red-ui/src/app/services/entity-services/platform-search.service.ts
+++ b/apps/red-ui/src/app/services/entity-services/platform-search.service.ts
@@ -18,7 +18,15 @@ export class PlatformSearchService extends GenericService {
super(_injector, 'search-v2');
}
- search({ dossierIds, query, includeDeletedDossiers, includeArchivedDossiers }: ISearchInput): Observable {
+ search({
+ dossierIds,
+ dossierTemplateIds,
+ workflowStatus,
+ assignee,
+ query,
+ includeDeletedDossiers,
+ includeArchivedDossiers,
+ }: ISearchInput): Observable {
if (!query) {
return of({
matchedDocuments: [],
@@ -28,6 +36,9 @@ export class PlatformSearchService extends GenericService {
const body: ISearchRequest = {
dossierIds,
+ dossierTemplateIds,
+ workflowStatus,
+ assignee,
queryString: query ?? '',
page: 0,
returnSections: false,
diff --git a/apps/red-ui/src/assets/i18n/de.json b/apps/red-ui/src/assets/i18n/de.json
index 3fa044f0e..09e61b815 100644
--- a/apps/red-ui/src/assets/i18n/de.json
+++ b/apps/red-ui/src/assets/i18n/de.json
@@ -690,7 +690,6 @@
},
"digital-signature-screen": {
"action": {
- "certificate-not-valid-error": "",
"delete-error": "Die digitale Signatur konnte nicht entfernt werden, bitte versuchen Sie es erneut.",
"delete-success": "Die digitale Signatur wurde gelöscht. Geschwärzte Dateien werden nicht länger mit einer Signatur versehen!",
"remove": "",
@@ -1460,13 +1459,11 @@
"labels": {
"download-cleanup-download-files-hours": "",
"download-cleanup-not-download-files-hours": "",
- "remove-digital-signature-on-upload": "",
"soft-delete-cleanup-time": ""
},
"placeholders": {
"download-cleanup-download-files-hours": "",
"download-cleanup-not-download-files-hours": "",
- "remove-digital-signature-on-upload": "",
"soft-delete-cleanup-time": ""
},
"title": ""
@@ -1887,9 +1884,13 @@
"status": "Status"
},
"filters": {
+ "assignee": "",
"by-dossier": "Nach Dossier filtern",
+ "by-template": "",
"only-active": "",
- "search-placeholder": "Dossiername..."
+ "search-by-template-placeholder": "",
+ "search-placeholder": "Dossiername...",
+ "status": ""
},
"missing": "Fehlt",
"must-contain": "Muss enthalten",
diff --git a/apps/red-ui/src/assets/i18n/en.json b/apps/red-ui/src/assets/i18n/en.json
index 6a412681c..6be17fe89 100644
--- a/apps/red-ui/src/assets/i18n/en.json
+++ b/apps/red-ui/src/assets/i18n/en.json
@@ -1460,13 +1460,11 @@
"labels": {
"download-cleanup-download-files-hours": "Deletion time (hours) for download packages that have been generated and downloaded",
"download-cleanup-not-download-files-hours": "Deletion time (hours) for download packages that have been generated but not yet downloaded",
- "remove-digital-signature-on-upload": "Remove digital signature on upload",
"soft-delete-cleanup-time": "Deletion time (hours) for deleted files in Trash"
},
"placeholders": {
"download-cleanup-download-files-hours": "(hours)",
"download-cleanup-not-download-files-hours": "(hours)",
- "remove-digital-signature-on-upload": "True / False",
"soft-delete-cleanup-time": "(hours)"
},
"title": "System Preferences"
@@ -1887,9 +1885,13 @@
"status": "Status"
},
"filters": {
- "by-dossier": "Filter by Dossier",
+ "assignee": "Assignee",
+ "by-dossier": "Dossier",
+ "by-template": "Dossier Template",
"only-active": "Active dossiers only",
- "search-placeholder": "Dossier name..."
+ "search-by-template-placeholder": "Dossier Template name...",
+ "search-placeholder": "Dossier name...",
+ "status": "Status"
},
"missing": "Missing",
"must-contain": "Must contain",
diff --git a/libs/common-ui b/libs/common-ui
index 9ee7b12e4..d4e2bd43a 160000
--- a/libs/common-ui
+++ b/libs/common-ui
@@ -1 +1 @@
-Subproject commit 9ee7b12e4a51c7d1ee8923dc87e359b12e5be118
+Subproject commit d4e2bd43ada36ae0b11be539ed613efec7e8ca57
diff --git a/libs/red-domain/src/lib/search/search-input.ts b/libs/red-domain/src/lib/search/search-input.ts
index c2524d142..020d8bac9 100644
--- a/libs/red-domain/src/lib/search/search-input.ts
+++ b/libs/red-domain/src/lib/search/search-input.ts
@@ -1,8 +1,12 @@
import { List } from '@iqser/common-ui';
+import { WorkflowFileStatus } from '../files';
export interface ISearchInput {
readonly query: string;
readonly dossierIds?: List;
+ readonly dossierTemplateIds?: List;
+ readonly workflowStatus?: WorkflowFileStatus;
+ readonly assignee?: string;
readonly includeDeletedDossiers: boolean;
readonly includeArchivedDossiers: boolean;
}
diff --git a/libs/red-domain/src/lib/search/search-list-item.ts b/libs/red-domain/src/lib/search/search-list-item.ts
index 70932948b..f8a815ddc 100644
--- a/libs/red-domain/src/lib/search/search-list-item.ts
+++ b/libs/red-domain/src/lib/search/search-list-item.ts
@@ -2,6 +2,7 @@ import { IListable, List } from '@iqser/common-ui';
export interface ISearchListItem extends IListable {
readonly dossierId: string;
+ readonly dossierTemplateId: string;
readonly filename: string;
readonly assignee: string;
readonly unmatched: List | null;
diff --git a/libs/red-domain/src/lib/search/search.request.ts b/libs/red-domain/src/lib/search/search.request.ts
index c142351d3..a7d19af88 100644
--- a/libs/red-domain/src/lib/search/search.request.ts
+++ b/libs/red-domain/src/lib/search/search.request.ts
@@ -1,4 +1,5 @@
import { List } from '@iqser/common-ui';
+import { WorkflowFileStatus } from '../files';
export interface ISearchRequest {
readonly dossierIds?: List;
@@ -10,4 +11,6 @@ export interface ISearchRequest {
readonly pageSize?: number;
readonly queryString?: string;
readonly returnSections?: boolean;
+ readonly workflowStatus?: WorkflowFileStatus;
+ readonly assignee?: string;
}