RED-9321: refactored trash and search modules.

This commit is contained in:
Nicoleta Panaghiu 2024-06-13 13:29:53 +03:00 committed by Dan Percic
parent 0fe7584f4b
commit c4912ffd8f
8 changed files with 111 additions and 104 deletions

View File

@ -147,7 +147,7 @@ const mainRoutes: IqserRoutes = [
},
{
path: 'search',
loadChildren: () => import('./modules/search/search.module').then(m => m.SearchModule),
loadComponent: () => import('./modules/search/search-screen/search-screen.component'),
canActivate: [CompositeRouteGuard, IqserPermissionsGuard, loadAllDossiersGuard()],
data: {
routeGuards: [IqserAuthGuard, RedRoleGuard],
@ -159,7 +159,7 @@ const mainRoutes: IqserRoutes = [
},
{
path: 'trash',
loadChildren: () => import('./modules/trash/trash.module').then(m => m.TrashModule),
loadChildren: () => import('./modules/trash/trash.routes'),
canActivate: [CompositeRouteGuard, IqserPermissionsGuard],
data: {
routeGuards: [IqserAuthGuard, RedRoleGuard, TrashGuard],

View File

@ -3,11 +3,31 @@ import { workflowFileStatusTranslations } from '@translations/file-status-transl
import { ISearchListItem } from '@red/domain';
import { escapeHtml } from '@iqser/common-ui/lib/utils';
import { getDossierRouterLink } from '@utils/router-links';
import { MatTooltip } from '@angular/material/tooltip';
import { NgForOf, NgIf } from '@angular/common';
import { TranslateModule } from '@ngx-translate/core';
import { StopPropagationDirective } from '@iqser/common-ui';
import { InitialsAvatarComponent } from '@common-ui/users';
import { StatusBarComponent } from '@common-ui/shared';
import { MatIcon } from '@angular/material/icon';
import { RouterLink } from '@angular/router';
@Component({
selector: 'redaction-search-item-template',
templateUrl: './search-item-template.component.html',
styleUrls: ['./search-item-template.component.scss'],
standalone: true,
imports: [
MatTooltip,
NgIf,
TranslateModule,
NgForOf,
StopPropagationDirective,
InitialsAvatarComponent,
StatusBarComponent,
MatIcon,
RouterLink,
],
})
export class SearchItemTemplateComponent implements OnChanges {
@Input() item: ISearchListItem;

View File

@ -1,11 +1,18 @@
import { ChangeDetectionStrategy, Component, OnDestroy } from '@angular/core';
import { ListingComponent, listingProvidersFactory, LoadingService, SearchPositions, TableColumnConfig } from '@iqser/common-ui';
import {
IqserListingModule,
ListingComponent,
listingProvidersFactory,
LoadingService,
SearchPositions,
TableColumnConfig,
} from '@iqser/common-ui';
import { combineLatest, 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 { TranslateModule, TranslateService } from '@ngx-translate/core';
import { RouterHistoryService } from '@services/router-history.service';
import {
Dossier,
@ -25,14 +32,18 @@ import { DossierTemplatesService } from '@services/dossier-templates/dossier-tem
import { UserService } from '@users/user.service';
import { IFilterGroup, keyChecker, NestedFilter } from '@iqser/common-ui/lib/filtering';
import { SortingOrders } from '@iqser/common-ui/lib/sorting';
import { AsyncPipe, NgIf } from '@angular/common';
import { SearchItemTemplateComponent } from '../search-item-template/search-item-template.component';
@Component({
templateUrl: './search-screen.component.html',
styleUrls: ['./search-screen.component.scss'],
providers: listingProvidersFactory(SearchScreenComponent),
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [IqserListingModule, TranslateModule, AsyncPipe, NgIf, SearchItemTemplateComponent],
})
export class SearchScreenComponent extends ListingComponent<ISearchListItem> implements OnDestroy {
export default class SearchScreenComponent extends ListingComponent<ISearchListItem> implements OnDestroy {
readonly searchPositions = SearchPositions;
readonly tableHeaderLabel = _('search-screen.table-header');
@ -46,7 +57,7 @@ export class SearchScreenComponent extends ListingComponent<ISearchListItem> imp
readonly searchResults$ = new Observable<ISearchListItem[]>();
readonly dossierTemplates$ = this._dossierTemplateService.loadAll().pipe(tap(templates => this._addTemplateFilter(templates)));
readonly dossierTemplates$ = this._dossierTemplateService.loadAll().pipe(tap(templates => this.#addTemplateFilter(templates)));
constructor(
private readonly _router: Router,
@ -65,10 +76,10 @@ export class SearchScreenComponent extends ListingComponent<ISearchListItem> imp
if (!Object.keys(this._activatedRoute.snapshot.queryParams).length) {
this._router.navigate(['main']).then();
}
this.searchResults$ = combineLatest([this._queryChanged$, this._filtersChanged$]).pipe(
this.searchResults$ = combineLatest([this.#queryChanged$, this.#filtersChanged$]).pipe(
tap(() => this._loadingService.start()),
tap(([query, [dossierIds, workflowStatus, assignee, dossierTemplateIds, onlyActive]]) =>
this._updateNavigation(query, dossierIds, workflowStatus, assignee, dossierTemplateIds, onlyActive),
this.#updateNavigation(query, dossierIds, workflowStatus, assignee, dossierTemplateIds, onlyActive),
),
switchMap(([query, [dossierIds, workflowStatus, assignee, dossierTemplateIds, onlyActive]]) =>
this._platformSearchService.search({
@ -81,43 +92,43 @@ export class SearchScreenComponent extends ListingComponent<ISearchListItem> imp
includeArchivedDossiers: !onlyActive,
}),
),
map(searchResult => this._toMatchedDocuments(searchResult)),
map(docs => this._toListItems(docs)),
map(searchResult => this.#toMatchedDocuments(searchResult)),
map(docs => this.#toListItems(docs)),
tap(result => this.entitiesService.setEntities(result)),
tap(() => this._loadingService.stop()),
);
this.searchService.skip = true;
this.sortingService.setSortingOption({ column: 'searchKey', order: SortingOrders.desc });
this._initFilters();
this.#initFilters();
}
private get _routeDossierIds(): string[] {
get #routeDossierIds(): string[] {
return this._activatedRoute.snapshot.queryParamMap.get('dossierIds').split(',');
}
private get _routeDossierTemplateIds(): string[] {
get #routeDossierTemplateIds(): string[] {
return this._activatedRoute.snapshot.queryParamMap.get('dossierTemplateIds')?.split(',');
}
private get _routeStatus(): WorkflowFileStatus {
get #routeStatus(): WorkflowFileStatus {
return this._activatedRoute.snapshot.queryParamMap.get('status') as WorkflowFileStatus;
}
private get _routeAssignee(): string {
get #routeAssignee(): string {
return this._activatedRoute.snapshot.queryParamMap.get('assignee');
}
private get _routeOnlyActive(): boolean {
get #routeOnlyActive(): boolean {
return this._activatedRoute.snapshot.queryParamMap.get('onlyActive') === 'true';
}
private get _routeQuery(): string {
get #routeQuery(): string {
return this._activatedRoute.snapshot.queryParamMap.get('query');
}
private get _queryChanged$(): Observable<string> {
get #queryChanged$(): Observable<string> {
return this.searchService.valueChanges$.pipe(
startWith(this._routeQuery),
startWith(this.#routeQuery),
tap(query => (this.searchService.searchValue = query)),
debounceTime(300),
);
@ -127,7 +138,7 @@ export class SearchScreenComponent extends ListingComponent<ISearchListItem> imp
return this._featuresService.isEnabled(DOSSIERS_ARCHIVE);
}
private get _filtersChanged$(): Observable<[string[], WorkflowFileStatus, string, string[], boolean]> {
get #filtersChanged$(): Observable<[string[], WorkflowFileStatus, string, string[], boolean]> {
const onlyActiveDossiers$ = this.#enabledArchive
? this.filterService.getSingleFilter('onlyActiveDossiers').pipe(map(f => !!f.checked))
: of(true);
@ -141,11 +152,11 @@ export class SearchScreenComponent extends ListingComponent<ISearchListItem> imp
return [dossierIds, workflowStatus, assignee, dossierTemplateIds, onlyActive];
}),
startWith<[string[], WorkflowFileStatus, string, string[], boolean]>([
this._routeDossierIds,
this._routeStatus,
this._routeAssignee,
this._routeDossierTemplateIds,
this._routeOnlyActive,
this.#routeDossierIds,
this.#routeStatus,
this.#routeAssignee,
this.#routeDossierTemplateIds,
this.#routeOnlyActive,
]),
);
}
@ -155,8 +166,8 @@ export class SearchScreenComponent extends ListingComponent<ISearchListItem> imp
this.searchService.searchValue = newQuery ?? '';
}
private _initFilters() {
const dossierIds = this._routeDossierIds;
#initFilters() {
const dossierIds = this.#routeDossierIds;
const dossierToFilter = ({ dossierName, id }: Dossier) => {
const checked = dossierIds.includes(id);
return new NestedFilter({ id, label: dossierName, checked });
@ -170,7 +181,7 @@ export class SearchScreenComponent extends ListingComponent<ISearchListItem> imp
checker: keyChecker('dossierId'),
};
const status = this._routeStatus;
const status = this.#routeStatus;
const statusToFilter = (workflowStatus: WorkflowFileStatus) => {
const checked = status === workflowStatus;
return new NestedFilter({
@ -188,7 +199,7 @@ export class SearchScreenComponent extends ListingComponent<ISearchListItem> imp
checker: keyChecker('status'),
};
const assignee = this._routeAssignee;
const assignee = this.#routeAssignee;
const assigneeToFilter = (userId: string) => {
const checked = assignee === userId;
return new NestedFilter({ id: userId, label: this._userService.getName(userId), checked });
@ -215,12 +226,12 @@ export class SearchScreenComponent extends ListingComponent<ISearchListItem> imp
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 });
this.filterService.addSingleFilter({ id: 'onlyActiveDossiers', label: onlyActiveLabel, checked: this.#routeOnlyActive });
}
}
private _addTemplateFilter(templates: DossierTemplate[]) {
const templatesIds = this._routeDossierTemplateIds;
#addTemplateFilter(templates: DossierTemplate[]) {
const templatesIds = this.#routeDossierTemplateIds;
const templateToFilter = ({ name, id }: DossierTemplate) => {
const checked = templatesIds?.includes(id);
return new NestedFilter({ id, label: name, checked });
@ -236,7 +247,7 @@ export class SearchScreenComponent extends ListingComponent<ISearchListItem> imp
this.filterService.addFilterGroups([templateNameFilter]);
}
private _updateNavigation(
#updateNavigation(
query?: string,
dossierIds?: string[],
workflowStatus?: WorkflowFileStatus,
@ -255,15 +266,15 @@ export class SearchScreenComponent extends ListingComponent<ISearchListItem> imp
return this._router.navigate([], { queryParams, replaceUrl: true });
}
private _toMatchedDocuments({ matchedDocuments }: ISearchResponse): IMatchedDocument[] {
#toMatchedDocuments({ matchedDocuments }: ISearchResponse): IMatchedDocument[] {
return matchedDocuments.filter(doc => doc.score > 0 && doc.matchedTerms.length > 0);
}
private _toListItems(matchedDocuments: IMatchedDocument[]): ISearchListItem[] {
return matchedDocuments.map(document => this._toListItem(document)).filter(value => value);
#toListItems(matchedDocuments: IMatchedDocument[]): ISearchListItem[] {
return matchedDocuments.map(document => this.#toListItem(document)).filter(value => value);
}
private _toListItem({
#toListItem({
dossierId,
fileId,
unmatchedTerms,

View File

@ -1,28 +0,0 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { SearchScreenComponent } from './search-screen/search-screen.component';
import { RouterModule } from '@angular/router';
import { IqserListingModule, StopPropagationDirective } from '@iqser/common-ui';
import { SharedModule } from '@shared/shared.module';
import { TranslateModule } from '@ngx-translate/core';
import { SearchItemTemplateComponent } from './search-item-template/search-item-template.component';
import { InitialsAvatarComponent, IqserUsersModule } from '@iqser/common-ui/lib/users';
import { StatusBarComponent } from '@iqser/common-ui/lib/shared';
const routes = [{ path: '', component: SearchScreenComponent }];
@NgModule({
declarations: [SearchScreenComponent, SearchItemTemplateComponent],
imports: [
CommonModule,
RouterModule.forChild(routes),
SharedModule,
IqserUsersModule,
TranslateModule,
IqserListingModule,
StatusBarComponent,
StopPropagationDirective,
InitialsAvatarComponent,
],
})
export class SearchModule {}

View File

@ -1,6 +1,13 @@
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { ListingComponent, listingProvidersFactory, LoadingService, TableColumnConfig } from '@iqser/common-ui';
import {
CircleButtonComponent,
IqserListingModule,
ListingComponent,
listingProvidersFactory,
LoadingService,
TableColumnConfig,
} from '@iqser/common-ui';
import { SortingOrders } from '@iqser/common-ui/lib/sorting';
import { TrashItem } from '@red/domain';
import { TrashService } from '@services/entity-services/trash.service';
@ -8,6 +15,9 @@ import { RouterHistoryService } from '@services/router-history.service';
import { firstValueFrom, Observable } from 'rxjs';
import { distinctUntilChanged, map } from 'rxjs/operators';
import { TrashDialogService } from '../services/trash-dialog.service';
import { TranslateModule } from '@ngx-translate/core';
import { AsyncPipe, NgIf } from '@angular/common';
import { TrashTableItemComponent } from './trash-table-item/trash-table-item.component';
@Component({
templateUrl: './trash-screen.component.html',
@ -16,11 +26,13 @@ import { TrashDialogService } from '../services/trash-dialog.service';
entitiesService: TrashService,
component: TrashScreenComponent,
}),
standalone: true,
imports: [IqserListingModule, TranslateModule, CircleButtonComponent, NgIf, TrashTableItemComponent, AsyncPipe],
})
export class TrashScreenComponent extends ListingComponent<TrashItem> implements OnInit {
readonly tableHeaderLabel = _('trash.table-header.title');
readonly canRestoreSelected$ = this._canRestoreSelected$;
readonly canHardDeleteSelected$ = this._canHardDeleteSelected$;
readonly canRestoreSelected$ = this.#canRestoreSelected$;
readonly canHardDeleteSelected$ = this.#canHardDeleteSelected$;
readonly tableColumnConfigs: TableColumnConfig<TrashItem>[] = [
{ label: _('trash.table-col-names.name'), sortByKey: 'name' },
{ label: _('trash.table-col-names.owner'), class: 'user-column', sortByKey: 'ownerName' },
@ -43,14 +55,14 @@ export class TrashScreenComponent extends ListingComponent<TrashItem> implements
});
}
private get _canRestoreSelected$(): Observable<boolean> {
get #canRestoreSelected$(): Observable<boolean> {
return this.listingService.selectedEntities$.pipe(
map(entities => entities.length && !entities.find(dossier => !(dossier.canRestore && dossier.hasRestoreRights))),
distinctUntilChanged(),
);
}
private get _canHardDeleteSelected$(): Observable<boolean> {
get #canHardDeleteSelected$(): Observable<boolean> {
return this.listingService.selectedEntities$.pipe(
map(entities => entities.length && !entities.find(dossier => !dossier.hasHardDeleteRights)),
distinctUntilChanged(),

View File

@ -2,15 +2,34 @@ import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, Out
import { Dossier, DossierStats, TrashFile, TrashItem, User } from '@red/domain';
import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service';
import { DossierStatsService } from '@services/dossiers/dossier-stats.service';
import { PartialDossier } from '@shared/components/dossier-name-column/dossier-name-column.component';
import { DossierNameColumnComponent, PartialDossier } from '@shared/components/dossier-name-column/dossier-name-column.component';
import { Observable } from 'rxjs';
import { getCurrentUser } from '@common-ui/users';
import { getCurrentUser, InitialsAvatarComponent } from '@common-ui/users';
import { MatIcon } from '@angular/material/icon';
import { FileNameColumnComponent } from '@shared/components/file-name-column/file-name-column.component';
import { AsyncPipe, DatePipe, NgIf } from '@angular/common';
import { CircleButtonComponent } from '@iqser/common-ui';
import { RouterLink } from '@angular/router';
import { TranslateModule } from '@ngx-translate/core';
@Component({
selector: 'redaction-trash-table-item [item]',
templateUrl: './trash-table-item.component.html',
styleUrls: ['./trash-table-item.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [
MatIcon,
FileNameColumnComponent,
DossierNameColumnComponent,
NgIf,
InitialsAvatarComponent,
CircleButtonComponent,
AsyncPipe,
RouterLink,
DatePipe,
TranslateModule,
],
})
export class TrashTableItemComponent implements OnChanges {
@Input() item: TrashItem;

View File

@ -1,32 +0,0 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
import { TrashScreenComponent } from './trash-screen/trash-screen.component';
import { CircleButtonComponent, IqserListingModule } from '@iqser/common-ui';
import { TrashTableItemComponent } from './trash-screen/trash-table-item/trash-table-item.component';
import { SharedModule } from '@shared/shared.module';
import { TrashDialogService } from './services/trash-dialog.service';
import { TranslateModule } from '@ngx-translate/core';
import { InitialsAvatarComponent, IqserUsersModule } from '@iqser/common-ui/lib/users';
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 routes = [{ path: '', component: TrashScreenComponent }];
@NgModule({
declarations: [TrashScreenComponent, TrashTableItemComponent],
imports: [
CommonModule,
RouterModule.forChild(routes),
SharedModule,
IqserUsersModule,
TranslateModule,
IqserListingModule,
CircleButtonComponent,
FileNameColumnComponent,
InitialsAvatarComponent,
DossierNameColumnComponent,
],
providers: [TrashDialogService],
})
export class TrashModule {}

View File

@ -0,0 +1,5 @@
import { TrashScreenComponent } from './trash-screen/trash-screen.component';
import { TrashDialogService } from './services/trash-dialog.service';
import { IqserRoutes } from '@iqser/common-ui';
export default [{ path: '', component: TrashScreenComponent, providers: [TrashDialogService] }] satisfies IqserRoutes;