diff --git a/src/frontend/app/actions/reconciliation-action/reconcile-action.component.html b/src/frontend/app/actions/reconciliation-action/reconcile-action.component.html index a787ac803f037bbdbe762b28ea1c64a0cd6150b9..17e168177620f56bc9e4fc82a1ca2d6120dc2c49 100755 --- a/src/frontend/app/actions/reconciliation-action/reconcile-action.component.html +++ b/src/frontend/app/actions/reconciliation-action/reconcile-action.component.html @@ -33,9 +33,12 @@ </div> </div> <div *ngIf="!noResourceToProcess" class="col-md-12"> - <app-criteria-tool (searchUrlGenerated)="launchSearch($event)" - [defaultCriteria]="['resourceField','contactField']"></app-criteria-tool> - <search-adv-list #appSearchAdvList [singleMode]="true" [excludeRes]="data.resIds"></search-adv-list> + <div class="bg-primary"> + <app-criteria-tool #appCriteriaTool [openedPanel]="true" (searchUrlGenerated)="appSearchResultList.launchSearch($event)" + [defaultCriteria]="['chrono', 'subject','recipients','senders']"></app-criteria-tool> + <div id="toolTemplate" style="padding-top: 10px;"></div> + </div> + <app-search-result-list #appSearchResultList [actionMode]="false" [hideFilter]="true" [appCriteriaTool]="appCriteriaTool" [standalone]="true" [singleSelection]="true"></app-search-result-list> </div> </ng-container> </div> @@ -43,7 +46,7 @@ <span class="divider-modal"></span> <div mat-dialog-actions class="actions"> <button mat-raised-button mat-button color="primary" - [disabled]="loading || (appSearchAdvList !== undefined && appSearchAdvList.getSelectedRessources().length === 0) || noResourceToProcess" + [disabled]="loading || !isSelectedResources() || noResourceToProcess" (click)="onSubmit()">{{'lang.validate' | translate}}</button> <button mat-raised-button mat-button [disabled]="loading" [mat-dialog-close]="">{{'lang.cancel' | translate}}</button> </div> diff --git a/src/frontend/app/actions/reconciliation-action/reconcile-action.component.scss b/src/frontend/app/actions/reconciliation-action/reconcile-action.component.scss index 70c84987f8a9f73fb0d3c9ed740e00cad2d9a129..6f5edece33072c19d583840c7575480b69b6b4bc 100644 --- a/src/frontend/app/actions/reconciliation-action/reconcile-action.component.scss +++ b/src/frontend/app/actions/reconciliation-action/reconcile-action.component.scss @@ -1,3 +1,11 @@ +@import '../../../css/vars.scss'; + + .highlight { font-size: 110%; +} + +.bg-primary { + padding: 10px; + background: $primary; } \ No newline at end of file diff --git a/src/frontend/app/actions/reconciliation-action/reconcile-action.component.ts b/src/frontend/app/actions/reconciliation-action/reconcile-action.component.ts index ef92b48e9126fa0692501d29783b0fd985b48467..2e778e197176b360168ec7936249b5fb0771be12 100644 --- a/src/frontend/app/actions/reconciliation-action/reconcile-action.component.ts +++ b/src/frontend/app/actions/reconciliation-action/reconcile-action.component.ts @@ -4,22 +4,21 @@ import { NotificationService } from '@service/notification/notification.service' import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { HttpClient } from '@angular/common/http'; import { NoteEditorComponent } from '../../notes/note-editor.component'; -import { tap, exhaustMap, finalize, catchError } from 'rxjs/operators'; +import { tap, finalize, catchError } from 'rxjs/operators'; import { of } from 'rxjs'; import { FunctionsService } from '@service/functions.service'; -import { SearchAdvListComponent } from '../../adv-search/list/search-adv-list.component'; +import { SearchResultListComponent } from '@appRoot/adv-search/result-list/search-result-list.component'; @Component({ - templateUrl: "reconcile-action.component.html", + templateUrl: 'reconcile-action.component.html', styleUrls: ['reconcile-action.component.scss'], }) export class ReconcileActionComponent implements OnInit { - loading: boolean = false; @ViewChild('noteEditor', { static: false }) noteEditor: NoteEditorComponent; - @ViewChild('appSearchAdvList', { static: false }) appSearchAdvList: SearchAdvListComponent; + @ViewChild('appSearchResultList', { static: false }) appSearchResultList: SearchResultListComponent; searchUrl: string = ''; resourcesErrors: any[] = []; @@ -49,28 +48,28 @@ export class ReconcileActionComponent implements OnInit { this.resourcesErrors = []; return new Promise((resolve, reject) => { - this.http.post('../rest/resourcesList/users/' + this.data.userId + '/groups/' + this.data.groupId + '/baskets/' + this.data.basketId + '/actions/' + this.data.action.id + '/checkReconcile', { resources: this.data.resIds, }) - .subscribe((data: any) => { - if(!this.functions.empty(data.resourcesInformations.error)) { - this.resourcesErrors = data.resourcesInformations.error; - } - if (data.resourcesInformations.success) { - data.resourcesInformations.success.forEach((value: any) => { - this.selectedRes.push(value.res_id); - }); - } - this.noResourceToProcess = this.resourcesErrors.length === this.data.resIds.length; - resolve(true); - }, (err: any) => { - this.notify.handleSoftErrors(err); - this.dialogRef.close(); - }); + this.http.post('../rest/resourcesList/users/' + this.data.userId + '/groups/' + this.data.groupId + '/baskets/' + this.data.basketId + '/actions/' + this.data.action.id + '/checkReconcile', { resources: this.data.resIds, }) + .subscribe((data: any) => { + if (!this.functions.empty(data.resourcesInformations.error)) { + this.resourcesErrors = data.resourcesInformations.error; + } + if (data.resourcesInformations.success) { + data.resourcesInformations.success.forEach((value: any) => { + this.selectedRes.push(value.res_id); + }); + } + this.noResourceToProcess = this.resourcesErrors.length === this.data.resIds.length; + resolve(true); + }, (err: any) => { + this.notify.handleSoftErrors(err); + this.dialogRef.close(); + }); }); } executeAction() { - - this.http.put(this.data.processActionRoute, { resources: this.selectedRes, data: {resId : this.appSearchAdvList.getSelectedRessources()[0]} }).pipe( + + this.http.put(this.data.processActionRoute, { resources: this.selectedRes, data: { resId: this.appSearchResultList.getSelectedResources()[0] } }).pipe( tap((data: any) => { if (data !== null && !this.functions.empty(data.errors)) { this.notify.error(data.errors); @@ -86,8 +85,7 @@ export class ReconcileActionComponent implements OnInit { ).subscribe(); } - launchSearch(value: any) { - this.searchUrl = value; - this.appSearchAdvList.refreshDao(value); + isSelectedResources() { + return this.appSearchResultList !== undefined && this.appSearchResultList.getSelectedResources().filter(res => this.data.resIds.indexOf(res) === -1).length > 0; } } diff --git a/src/frontend/app/adv-search/adv-search.component.html b/src/frontend/app/adv-search/adv-search.component.html index 7434bad432e34d26f428e388868d02390c1ab2ff..df96d808349f63822aa23066069d2afc2267641f 100644 --- a/src/frontend/app/adv-search/adv-search.component.html +++ b/src/frontend/app/adv-search/adv-search.component.html @@ -1,11 +1,8 @@ <mat-sidenav-container class="maarch-container"> <ng-template #adminMenuTemplate> - <app-filter-tool-adv-search *ngIf="data.length > 0"></app-filter-tool-adv-search> + <div id="filterTemplate"></div> </ng-template> <mat-sidenav-content> - <mat-card id="viewThumbnail" style="display:none;position: fixed;z-index: 2;margin-left: 1px;"> - <img style="max-height: 100vh;" [src]="thumbnailUrl | secureUrl | async" /> - </mat-card> <div class="bg-head"> <div class="bg-head-title" [class.customContainerRight]="appService.getViewMode()"> <div class="bg-head-title-label"> @@ -16,211 +13,14 @@ </div> </div> <div class="bg-head-content" [class.fullContainer]="appService.getViewMode()"> - <app-criteria-tool #appCriteriaTool (searchUrlGenerated)="launchSearch($event)" - [defaultCriteria]="['doctype','subject']" style="width:100%;padding-bottom: 10px;" (loaded)="initSavedCriteria()"></app-criteria-tool> - <div *ngIf="initSearch" class="filtersContent"> - <div style="flex: 1"></div> - <div class="orderTool"> - <mat-form-field class="basket-order"> - <mat-icon matPrefix class="fa fa-list"></mat-icon> - <mat-select [(ngModel)]="this.listProperties.order" (selectionChange)="updateFilters()"> - <mat-option [value]="column.id" *ngFor="let column of displayColsOrder"> - {{'lang.' + column.id | translate}} - </mat-option> - </mat-select> - </mat-form-field> - </div> - <div class="ascDescTool"> - <button [disabled]="this.listProperties.order == ''" - [style.opacity]="this.listProperties.order == '' ? '0.2' : '1'" mat-fab - [title]="this.listProperties.orderDir == 'DESC' ? this.translate.instant('lang.descOrder') : this.translate.instant('lang.ascOrder')" - style="color: rgba(0,0,0,0.38);" (click)="changeOrderDir();"> - <mat-icon *ngIf="this.listProperties.orderDir == 'DESC'" fontSet="fas" fontIcon="fa-sort-amount-down fa-2x"> - </mat-icon> - <mat-icon *ngIf="this.listProperties.orderDir == 'ASC'" fontSet="fas" fontIcon="fa-sort-amount-up fa-2x"> - </mat-icon> - </button> - </div> - </div> - <div class="filterBadges"> - <span *ngIf="!emptyCriteria()" class="label badge-eraser" (click)="removeCriteria('_ALL')" - title="{{'lang.eraseAllFilters' | translate}}"><i class="fas fa-eraser"></i></span> - <ng-container *ngFor="let critKey of criteria | keyvalue"> - <ng-container - *ngIf="isArrayType(critKey.value.values) && critKey.value.values.length <= 3"> - <span class="label badge-search" *ngFor="let val of critKey.value.values" - [title]="appCriteriaTool.getLabelValue(critKey.key,val)" - (click)="removeCriteria(critKey.key, val)"><i - class="fa {{indexingFieldService.getField(critKey.key).icon}}" - [title]="indexingFieldService.getField(critKey.key).label"></i> {{appCriteriaTool.getLabelValue(critKey.key,val)}} <i class="fa fa-times-circle"></i></span> - </ng-container> - <ng-container *ngIf="isArrayType(critKey.value.values) && critKey.value.values.length > 3"> - <span class="label badge-search" - [title]="appCriteriaTool.getLabelValues(critKey.key,critKey.value.values)" - (click)="removeCriteria(critKey.key)"><i - class="fa {{indexingFieldService.getField(critKey.key).icon}}" [title]="indexingFieldService.getField(critKey.key).label"></i> {{critKey.value.values.length}} valeurs <i class="fa fa-times-circle"></i></span> - </ng-container> - <ng-container *ngIf="!isArrayType(critKey.value.values) && critKey.key !== 'meta'"> - <span class="label badge-search" [title]="appCriteriaTool.getFormatLabel(critKey.key,critKey.value.values)" (click)="removeCriteria(critKey.key)"><i - class="fa {{indexingFieldService.getField(critKey.key).icon}}" - [title]="indexingFieldService.getField(critKey.key).label"></i> {{appCriteriaTool.getFormatLabel(critKey.key,critKey.value.values)}} <i class="fa fa-times-circle"></i></span> - </ng-container> - <ng-container *ngIf="!isArrayType(critKey.value.values) && critKey.key === 'meta'"> - <span class="label badge-search" [title]="'meta'" (click)="removeCriteria(critKey.key)">{{critKey.value.values}} <i class="fa fa-times-circle"></i></span> - </ng-container> - </ng-container> - </div> + <app-criteria-tool #appCriteriaTool (searchUrlGenerated)="appSearchResultList.launchSearch($event);" + [defaultCriteria]="['doctype','subject']" style="width:100%;padding-bottom: 10px;" (loaded)="appSearchResultList.initSavedCriteria()"></app-criteria-tool> + <div id="toolTemplate" style="width: 100%;"></div> </div> </div> <div class="container" [class.fullContainer]="appService.getViewMode()"> <div class="container-content"> - <div class="example-loading-shade" *ngIf="isLoadingResults"> - <mat-spinner *ngIf="isLoadingResults"></mat-spinner> - </div> - <div class="table-head"> - <div class="table-head-result"> - <mat-checkbox color="primary" - [checked]="selectedRes.length == allResInBasket.length && selectedRes.length > 0" - [indeterminate]="selectedRes.length > 0 && selectedRes.length < allResInBasket.length" - style="margin: 10px;padding-right: 10px;" - title="{{'lang.selectAllResInBasket' | translate}}" (change)="toggleAllRes($event)"> - </mat-checkbox> {{resultsLength}} - {{'lang.records' | translate | ucfirst}} <small *ngIf="selectedRes.length > 0">- - {{selectedRes.length}} - {{'lang.selected' | translate}}</small> - </div> - <div class="table-head-tool"> - <span> - <mat-paginator #paginatorResultList [length]="resultsLength" - [pageSizeOptions]="[10, 25, 50, 100, 150]" class="paginatorResultList"></mat-paginator> - </span> - <span> - <span> - <app-tools-list #actionsList [selectedRes]="selectedRes"></app-tools-list> - </span> - <span> - <app-followed-action-list #actionsList [contextMode]="false" [currentFolderInfo]="folderInfo" - [totalRes]="allResInBasket.length" [selectedRes]="selectedRes" - (refreshEvent)="refreshDaoAfterAction()"> - </app-followed-action-list> - </span> - </span> - </div> - </div> - <div style="height:90%;overflow:auto;position:absolute;width:100%;"> - <table #tableBasketListSort="matSort" cdkDropList id="folder-list" - [cdkDropListConnectedTo]="listTodrag()" [cdkDropListData]="data" - [cdkDropListDisabled]="dragInit || appService.getViewMode()" mat-table [dataSource]="data" - matSort matSortActive="resId" matSortDisableClear matSortDirection="asc" style="width:100%;"> - - <ng-container matColumnDef="resId"> - <td mat-cell *matCellDef="let row" - style="padding:0;border-top: solid 1px rgba(0, 0, 0, 0.12);" - [class.selected-data]="row.checked"> - <div class="main-info"> - <span style="width:50px;"> - <mat-checkbox color="primary" [checked]="row.checked" - (change)="toggleRes($event,row)" (click)="$event.stopPropagation();"> - </mat-checkbox> - </span> - <button mat-icon-button (click)="$event.stopPropagation();toggleMailTracking(row)" - style="margin-left: -25px;" class="followIcon" - [title]="row.mailTracking === true ? this.translate.instant('lang.untrackThisMail') : this.translate.instant('lang.trackThisMail')"> - <mat-icon - [ngClass]="[row.mailTracking === true ? 'fas fa-star' : 'far fa-star']" - style="margin-bottom: 5px;"></mat-icon> - </button> - <span style="cursor:pointer;" - class="main-info-status" (click)="launch(row);"> - <mat-icon *ngIf="row.isLocked !== true" title="{{row.statusLabel}}" - [ngStyle]="{'color': row.priorityColor}" style="width: 100%;" color="primary" - class="{{row.statusImage.charAt(0)}}{{row.statusImage.charAt(1)}} {{row.statusImage}} {{row.statusImage.charAt(0)}}{{row.statusImage.charAt(1)}}-2x"> - </mat-icon> - <span *ngIf="row.confidentiality === 'Y'" - class="watermark">{{'lang.confidential' | translate}}</span> - <mat-icon *ngIf="row.isLocked === true" - title="{{'lang.warnLockResInProgress' | translate}} : {{row.locker}}" - style="color: red;" class="fa fa-lock fa-2x"> - </mat-icon> - </span> - <span *ngIf="!appService.getViewMode()" (click)="launch(row);" class="main-info-data" - style="width:200px;text-align:center;cursor:pointer;"> - <ng-container - *ngIf="row.chrono == this.translate.instant('lang.undefined') && row.barcode != this.translate.instant('lang.undefined')"> - <span style="color: rgba(0,0,0,0.4);font-size: 90%;"> - <i title="{{'lang.barcode' | translate}}" class="fas fa-barcode"></i> - {{row.barcode}}</span> - </ng-container> - <ng-container *ngIf="row.chrono != this.translate.instant('lang.undefined')"> - {{row.chrono}} - </ng-container> - </span> - <span class="main-info-data" (click)="launch(row);" style="font-weight:bold;flex:1;cursor:pointer;" - [class.undefined]="row.subject == this.translate.instant('lang.undefined')" - title="{{row.subject}}">{{row.subject | shorten: 150: '...'}}</span> - <span class="main-info-action"> - <button mat-icon-button title="{{'lang.notes' | translate}}" - (click)="$event.stopPropagation();togglePanel('note',row)" - [class.noData]="row.countNotes == 0"> - <mat-icon matBadgeHidden="{{row.countNotes == 0}}" fontSet="fas" - matBadge="{{row.countNotes}}" fontIcon="fa-comments fa-2x" - [color]="snav2.opened && row.checked && currentMode == 'note' ? 'primary' : ''"> - </mat-icon> - </button> - <button mat-icon-button title="{{'lang.attachments' | translate}}" - (click)="$event.stopPropagation();togglePanel('attachment',row)" - [class.noData]="row.countAttachments == 0"> - <mat-icon matBadgeHidden="{{row.countAttachments == 0}}" fontSet="fas" - matBadge="{{row.countAttachments}}" fontIcon="fa-paperclip fa-2x" - [color]="snav2.opened && row.checked && currentMode == 'attachment' ? 'primary' : ''"> - </mat-icon> - </button> - <button mat-icon-button title="{{'lang.diffusionList' | translate}}" - (click)="$event.stopPropagation();togglePanel('diffusion',row)"> - <mat-icon fontSet="fas" fontIcon="fa-sitemap fa-2x" - [color]="snav2.opened && row.checked && currentMode == 'diffusion' ? 'primary' : ''"> - </mat-icon> - </button> - <button mat-icon-button *ngIf="!appService.getViewMode()" - title="{{row.hasDocument ? this.translate.instant('lang.viewResource') : this.translate.instant('lang.noDocument')}}" - (click)="$event.stopPropagation();viewDocument(row)" - (mouseenter)="viewThumbnail(row);" (mouseleave)="closeThumbnail();" - [disabled]="!row.hasDocument"> - <mat-icon class="fa" - [ngClass]="[row.hasDocument ? 'fa-eye' : 'fa-eye-slash']"> - </mat-icon> - </button> - <button mat-icon-button *ngIf="!appService.getViewMode()" title="{{'lang.linkDetails' | translate}}" - (click)="$event.stopPropagation();goToDetail(row);"> - <mat-icon fontSet="fas" fontIcon="fa-info-circle fa-2x"></mat-icon> - </button> - </span> - </div> - <div *ngIf="row.folders !== undefined && row.folders.length > 0" class="folder-info"> - <span class="badge badge-folder" - *ngFor="let folder of row.folders | sortBy : 'label'" - (click)="$event.stopPropagation();goToFolder(folder);" - title="{{'lang.goToFolder' | translate}} : {{folder.label}}"><i - class="fa fa-folder"></i> {{folder.label}}</span> - </div> - </td> - </ng-container> - <tr mat-row *matRowDef="let row; columns: displayedColumnsBasket;" - (contextmenu)="open($event,row);" class="rowData" - style="cursor: pointer;" [class.locked]="row.isLocked == true" cdkDrag - [cdkDragDisabled]="!row.allowed" (cdkDragStarted)="selectSpecificRes(row);" - [cdkDragData]="row"> - <div class="example-custom-placeholder" *cdkDragPlaceholder></div> - <div class="dragPreview" *cdkDragPreview><i class="fas fa-envelope-open-text fa-2x"></i> - <br /> - {{'lang.classifyInFolder' | translate}} : - <b>{{row.chrono}}</b> - </div> - </tr> - </table> - </div> - <div class="table-head"> - </div> + <app-search-result-list #appSearchResultList [searchTerm]="searchTerm" [appCriteriaTool]="appCriteriaTool" [sidenavRight]="sidenavRight"></app-search-result-list> </div> </div> </mat-sidenav-content> @@ -228,20 +28,6 @@ [opened]="appService.getViewMode() ? false : false" [mode]="appService.getViewMode() ? 'over' : 'side'" class="panel-right" style="overflow-x:hidden;" [class.docView]="innerHtml" [ngStyle]="{'width': appService.getViewMode() ? '80%' : '30%'}" autoFocus="false"> - <div *ngIf="innerHtml" [matTooltip]="currentChrono" [innerHTML]="innerHtml" - style="height: 100%;overflow: hidden;"></div> - - <div style="display:flex;position: sticky;top: 0px;z-index: 2;"> - <button mat-icon-button (click)="snav2.close()" style="font-size: 20px;color:#666;"> - <mat-icon class="fa fa-arrow-right"></mat-icon> - </button> - </div> - <app-panel-list #appPanelList (refreshBadgeNotes)="refreshBadgeNotes($event)" - (refreshBadgeAttachments)="refreshBadgeAttachments($event)"></app-panel-list> - <mat-divider></mat-divider> + <div id="panelTemplate"></div> </mat-sidenav> </mat-sidenav-container> -<app-followed-action-list #actionsListContext [contextMode]="true" [currentFolderInfo]="folderInfo" - [totalRes]="allResInBasket.length" [selectedRes]="selectedRes" (refreshEvent)="refreshDaoAfterAction()" - (refreshPanelFolders)="foldersService.getFolders()"> -</app-followed-action-list> diff --git a/src/frontend/app/adv-search/adv-search.component.ts b/src/frontend/app/adv-search/adv-search.component.ts index 86c54e003d856d53f5cd89deb995d61b793e3062..2a5fc16f9cbaac610261aefa807229ab1801f4cd 100644 --- a/src/frontend/app/adv-search/adv-search.component.ts +++ b/src/frontend/app/adv-search/adv-search.component.ts @@ -1,148 +1,44 @@ -import { Component, OnInit, ViewChild, EventEmitter, ViewContainerRef, OnDestroy, TemplateRef } from '@angular/core'; -import { HttpClient } from '@angular/common/http'; +import { Component, OnInit, ViewChild, EventEmitter, ViewContainerRef, TemplateRef } from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; -import { NotificationService } from '@service/notification/notification.service'; -import { MatDialog, MatDialogRef } from '@angular/material/dialog'; -import { MatPaginator } from '@angular/material/paginator'; +import { MatDialogRef } from '@angular/material/dialog'; import { MatSidenav } from '@angular/material/sidenav'; -import { MatSort } from '@angular/material/sort'; -import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; -import { startWith, switchMap, map, catchError, takeUntil, tap } from 'rxjs/operators'; -import { ActivatedRoute, Router } from '@angular/router'; +import { ActivatedRoute } from '@angular/router'; import { HeaderService } from '@service/header.service'; -import { Overlay } from '@angular/cdk/overlay'; -import { PanelListComponent } from '../list/panel/panel-list.component'; import { AppService } from '@service/app.service'; -import { BasketHomeComponent } from '../basket/basket-home.component'; -import { FolderActionListComponent } from '../folder/folder-action-list/folder-action-list.component'; -import { FoldersService } from '../folder/folders.service'; import { FunctionsService } from '@service/functions.service'; -import { of, merge, Subject, Subscription, Observable } from 'rxjs'; import { CriteriaToolComponent } from './criteria-tool/criteria-tool.component'; -import { IndexingFieldsService } from '@service/indexing-fields.service'; -import { CriteriaSearchService } from '@service/criteriaSearch.service'; +import { SearchResultListComponent } from './result-list/search-result-list.component'; -declare var $: any; @Component({ templateUrl: 'adv-search.component.html', styleUrls: ['adv-search.component.scss'] }) -export class AdvSearchComponent implements OnInit, OnDestroy { +export class AdvSearchComponent implements OnInit { - loading: boolean = true; - initSearch: boolean = false; - docUrl: string = ''; - public innerHtml: SafeHtml; - searchUrl: string = '../rest/search'; searchTerm: string = ''; - criteria: any = {}; - homeData: any; - - injectDatasParam = { - resId: 0, - editable: false - }; - currentResource: any = {}; filtersChange = new EventEmitter(); - dragInit: boolean = true; - dialogRef: MatDialogRef<any>; @ViewChild('snav2', { static: true }) sidenavRight: MatSidenav; - displayedColumnsBasket: string[] = ['resId']; - - displayedMainData: any = [ - { - 'value': 'chrono', - 'cssClasses': ['softColorData', 'align_centerData', 'chronoData'], - 'icon': '' - }, - { - 'value': 'subject', - 'cssClasses': ['longData'], - 'icon': '' - } - ]; - - resultListDatabase: ResultListHttpDao | null; - data: any = []; - resultsLength = 0; - isLoadingResults = true; - listProperties: any = {}; - currentChrono: string = ''; - currentMode: string = ''; - - thumbnailUrl: string = ''; - - selectedRes: Array<number> = []; - allResInBasket: number[] = []; - selectedDiffusionTab: number = 0; - folderInfo: any = { - id: 0, - 'label': '', - 'ownerDisplayName': '', - 'entitiesSharing': [] - }; - folderInfoOpened: boolean = false; - - private destroy$ = new Subject<boolean>(); - subscription: Subscription; - - displayColsOrder = [ - { 'id': 'destUser' }, - { 'id': 'categoryId' }, - { 'id': 'creationDate' }, - { 'id': 'processLimitDate' }, - { 'id': 'entityLabel' }, - { 'id': 'subject' }, - { 'id': 'chrono' }, - { 'id': 'priority' }, - { 'id': 'status' }, - { 'id': 'typeLabel' } - ]; - @ViewChild('adminMenuTemplate', { static: true }) adminMenuTemplate: TemplateRef<any>; - @ViewChild('actionsListContext', { static: true }) actionsList: FolderActionListComponent; - @ViewChild('appPanelList', { static: true }) appPanelList: PanelListComponent; + @ViewChild('appSearchResultList', { static: false }) appSearchResultList: SearchResultListComponent; @ViewChild('appCriteriaTool', { static: true }) appCriteriaTool: CriteriaToolComponent; - currentSelectedChrono: string = ''; - - @ViewChild(MatPaginator, { static: true }) paginator: MatPaginator; - @ViewChild('tableBasketListSort', { static: true }) sort: MatSort; - @ViewChild('basketHome', { static: true }) basketHome: BasketHomeComponent; - constructor( - private _activatedRoute: ActivatedRoute, + _activatedRoute: ActivatedRoute, public translate: TranslateService, - private router: Router, - private route: ActivatedRoute, - public http: HttpClient, - public dialog: MatDialog, - private sanitizer: DomSanitizer, private headerService: HeaderService, - public criteriaSearchService: CriteriaSearchService, - private notify: NotificationService, - public overlay: Overlay, public viewContainerRef: ViewContainerRef, public appService: AppService, - public foldersService: FoldersService, - public functions: FunctionsService, - public indexingFieldService: IndexingFieldsService) { + public functions: FunctionsService) { _activatedRoute.queryParams.subscribe( params => { if (!this.functions.empty(params.value)) { this.searchTerm = params.value; - this.initSearch = true; - this.criteria = { - meta: { - values: this.searchTerm - } - }; } } ); @@ -150,362 +46,7 @@ export class AdvSearchComponent implements OnInit, OnDestroy { ngOnInit(): void { this.headerService.sideBarAdmin = true; - - this.isLoadingResults = false; - this.headerService.injectInSideBarLeft(this.adminMenuTemplate, this.viewContainerRef, 'adminMenu'); this.headerService.setHeader(this.translate.instant('lang.searchMails'), '', ''); - - this.listProperties = this.criteriaSearchService.initListsProperties(this.headerService.user.id); - - this.loading = false; - } - - initSavedCriteria() { - if (Object.keys(this.listProperties.criteria).length > 0) { - const obj = { query: [] }; - Object.keys(this.listProperties.criteria).forEach(key => { - const objectItem = {}; - objectItem['identifier'] = key; - objectItem['values'] = this.listProperties.criteria[key].values; - obj.query.push(objectItem); - }); - this.appCriteriaTool.selectSearchTemplate(obj, false); - this.criteria = this.listProperties.criteria; - this.initResultList(); - } else if (this.initSearch) { - this.initResultList(); - } else { - this.appCriteriaTool.toggleTool(true); - } - } - - ngOnDestroy() { - this.destroy$.next(true); - } - - launch(row: any) { - const thisSelect = { checked: true }; - const thisDeselect = { checked: false }; - row.checked = true; - this.toggleAllRes(thisDeselect); - this.toggleRes(thisSelect, row); - this.router.navigate([`/resources/${row.resId}`]); - } - - launchSearch(criteria: any) { - this.criteria = JSON.parse(JSON.stringify(criteria)); - if (!this.initSearch) { - this.initResultList(); - this.initSearch = true; - } else { - this.refreshDao(); - } - this.appCriteriaTool.toggleTool(false); - } - - initResultList() { - this.resultListDatabase = new ResultListHttpDao(this.http, this.criteriaSearchService); - // If the user changes the sort order, reset back to the first page. - this.paginator.pageIndex = this.listProperties.page; - this.paginator.pageSize = this.listProperties.pageSize; - this.sort.sortChange.subscribe(() => this.paginator.pageIndex = 0); - - // When list is refresh (sort, page, filters) - merge(this.sort.sortChange, this.paginator.page, this.filtersChange) - .pipe( - takeUntil(this.destroy$), - startWith({}), - switchMap(() => { - this.isLoadingResults = true; - return this.resultListDatabase!.getRepoIssues( - this.sort.active, this.sort.direction, this.paginator.pageIndex, this.searchUrl, this.listProperties, this.paginator.pageSize, this.criteria); - }), - map((data: any) => { - // Flip flag to show that loading has finished. - this.isLoadingResults = false; - data = this.processPostData(data); - this.resultsLength = data.count; - this.allResInBasket = data.allResources; - // this.headerService.setHeader('Dossier : ' + this.folderInfo.label); - return data.resources; - }), - catchError((err: any) => { - this.notify.handleErrors(err); - // this.router.navigate(['/home']); - this.isLoadingResults = false; - return of(false); - }) - ).subscribe(data => this.data = data); - } - - goTo(row: any) { - // this.criteriaSearchService.filterMode = false; - if (this.docUrl === '../rest/resources/' + row.resId + '/content' && this.sidenavRight.opened) { - this.sidenavRight.close(); - } else { - this.docUrl = '../rest/resources/' + row.resId + '/content'; - this.currentChrono = row.chrono; - this.innerHtml = this.sanitizer.bypassSecurityTrustHtml( - '<iframe style=\'height:100%;width:100%;\' src=\'' + this.docUrl + '\' class=\'embed-responsive-item\'>' + - '</iframe>'); - this.sidenavRight.open(); - } - } - - goToDetail(row: any) { - this.router.navigate([`/resources/${row.resId}`]); - } - - goToFolder(folder: any) { - this.router.navigate([`/folders/${folder.id}`]); - } - - togglePanel(mode: string, row: any) { - const thisSelect = { checked: true }; - const thisDeselect = { checked: false }; - row.checked = true; - this.toggleAllRes(thisDeselect); - this.toggleRes(thisSelect, row); - - if (this.currentResource.resId === row.resId && this.sidenavRight.opened && this.currentMode === mode) { - this.sidenavRight.close(); - } else { - this.currentMode = mode; - this.currentResource = row; - this.appPanelList.loadComponent(mode, row); - this.sidenavRight.open(); - } - } - - refreshBadgeNotes(nb: number) { - this.currentResource.countNotes = nb; - } - - refreshFolderInformations() { - this.http.get('../rest/folders/' + this.folderInfo.id) - .subscribe((data: any) => { - const keywordEntities = [{ - keyword: 'ALL_ENTITIES', - text: this.translate.instant('lang.allEntities'), - }]; - this.folderInfo = { - 'id': data.folder.id, - 'label': data.folder.label, - 'ownerDisplayName': data.folder.ownerDisplayName, - 'entitiesSharing': data.folder.sharing.entities.map((entity: any) => { - if (!this.functions.empty(entity.label)) { - return entity.label; - } else { - return keywordEntities.filter((element: any) => element.keyword === entity.keyword)[0].text; - } - }), - }; - this.headerService.setHeader(this.folderInfo.label, '', 'fa fa-folder-open'); - }); - } - - refreshBadgeAttachments(nb: number) { - this.currentResource.countAttachments = nb; - } - - refreshDao() { - this.paginator.pageIndex = this.listProperties.page; - this.filtersChange.emit(); - } - - refreshDaoAfterAction() { - this.sidenavRight.close(); - this.refreshDao(); - const e: any = { checked: false }; - this.toggleAllRes(e); - } - - viewThumbnail(row: any) { - if (row.hasDocument) { - this.thumbnailUrl = '../rest/resources/' + row.resId + '/thumbnail'; - $('#viewThumbnail').show(); - $('#listContent').css({ 'overflow': 'hidden' }); - } - } - - closeThumbnail() { - $('#viewThumbnail').hide(); - $('#listContent').css({ 'overflow': 'auto' }); - } - - processPostData(data: any) { - data.resources.forEach((element: any) => { - // Process main datas - Object.keys(element).forEach((key) => { - if (key === 'statusImage' && element[key] == null) { - element[key] = 'fa-question undefined'; - } else if ((element[key] == null || element[key] === '') && ['closingDate', 'countAttachments', 'countNotes', 'display', 'mailTracking', 'hasDocument'].indexOf(key) === -1) { - element[key] = this.translate.instant('lang.undefined'); - } - }); - - element['checked'] = this.selectedRes.indexOf(element['resId']) !== -1; - }); - - return data; - } - - toggleRes(e: any, row: any) { - if (e.checked) { - if (this.selectedRes.indexOf(row.resId) === -1) { - this.selectedRes.push(row.resId); - row.checked = true; - } - } else { - const index = this.selectedRes.indexOf(row.resId); - this.selectedRes.splice(index, 1); - row.checked = false; - } - } - - toggleAllRes(e: any) { - this.selectedRes = []; - if (e.checked) { - this.data.forEach((element: any) => { - element['checked'] = true; - }); - this.selectedRes = JSON.parse(JSON.stringify(this.allResInBasket)); - } else { - this.data.forEach((element: any) => { - element['checked'] = false; - }); - } - } - - selectSpecificRes(row: any) { - const thisSelect = { checked: true }; - const thisDeselect = { checked: false }; - - this.toggleAllRes(thisDeselect); - this.toggleRes(thisSelect, row); - } - - open({ x, y }: MouseEvent, row: any) { - - const thisSelect = { checked: true }; - const thisDeselect = { checked: false }; - if (row.checked === false) { - row.checked = true; - this.toggleAllRes(thisDeselect); - this.toggleRes(thisSelect, row); - } - this.actionsList.open(x, y, row); - - // prevents default - return false; - } - - listTodrag() { - return this.foldersService.getDragIds(); - } - - toggleMailTracking(row: any) { - if (!row.mailTracking) { - this.http.post('../rest/resources/follow', { resources: [row.resId] }).pipe( - tap(() => { - this.headerService.nbResourcesFollowed++; - row.mailTracking = !row.mailTracking; - }), - catchError((err: any) => { - this.notify.handleSoftErrors(err); - return of(false); - }) - ).subscribe(); - } else { - this.http.request('DELETE', '../rest/resources/unfollow', { body: { resources: [row.resId] } }).pipe( - tap(() => { - this.headerService.nbResourcesFollowed--; - row.mailTracking = !row.mailTracking; - }), - catchError((err: any) => { - this.notify.handleSoftErrors(err); - return of(false); - }) - ).subscribe(); - } - } - - viewDocument(row: any) { - this.http.get(`../rest/resources/${row.resId}/content?mode=view`, { responseType: 'blob' }).pipe( - tap((data: any) => { - const file = new Blob([data], { type: 'application/pdf' }); - const fileURL = URL.createObjectURL(file); - const newWindow = window.open(); - newWindow.document.write(`<iframe style="width: 100%;height: 100%;margin: 0;padding: 0;" src="${fileURL}" frameborder="0" allowfullscreen></iframe>`); - newWindow.document.title = row.chrono; - }), - catchError((err: any) => { - this.notify.handleSoftErrors(err); - return of(false); - }) - ).subscribe(); - } - - emptyCriteria() { - return Object.keys(this.criteria).length === 0; - } - - isArrayType(value: any) { - return (Array.isArray(value)); - } - - removeCriteria(identifier: string, value: any = null) { - if (identifier !== '_ALL') { - const tmpArrCrit = []; - if (value === null || this.criteria[identifier].values.length === 1) { - this.criteria[identifier].values = []; - } else { - const indexArr = this.criteria[identifier].values.indexOf(value); - this.criteria[identifier].values.splice(indexArr, 1); - } - } else { - Object.keys(this.criteria).forEach(key => { - this.criteria[key].values = []; - }); - } - this.appCriteriaTool.refreshCriteria(this.criteria); - } - - updateFilters() { - this.listProperties.page = 0; - - this.criteriaSearchService.updateListsProperties(this.listProperties); - - this.refreshDao(); - } - - changeOrderDir() { - if (this.listProperties.orderDir === 'ASC') { - this.listProperties.orderDir = 'DESC'; - } else { - this.listProperties.orderDir = 'ASC'; - } - this.updateFilters(); - } -} -export interface BasketList { - folder: any; - resources: any[]; - countResources: number; - allResources: number[]; -} - -export class ResultListHttpDao { - - constructor(private http: HttpClient, private criteriaSearchService: CriteriaSearchService) { } - - getRepoIssues(sort: string, order: string, page: number, href: string, filters: any, pageSize: number, criteria: any): Observable<BasketList> { - this.criteriaSearchService.updateListsPropertiesPage(page); - this.criteriaSearchService.updateListsPropertiesPageSize(pageSize); - this.criteriaSearchService.updateListsPropertiesCriteria(criteria); - const offset = page * pageSize; - const requestUrl = `${href}?limit=${pageSize}&offset=${offset}&order=${filters.order}&orderDir=${filters.orderDir}`; - return this.http.post<BasketList>(requestUrl, this.criteriaSearchService.formatDatas(JSON.parse(JSON.stringify(criteria)))); } } diff --git a/src/frontend/app/adv-search/criteria-tool/criteria-tool.component.html b/src/frontend/app/adv-search/criteria-tool/criteria-tool.component.html index 91a63f79e793804b110ab60bff477553bdc44bf7..40ddc4ea8ab7c0006034800ac06f2bd99eff28d4 100755 --- a/src/frontend/app/adv-search/criteria-tool/criteria-tool.component.html +++ b/src/frontend/app/adv-search/criteria-tool/criteria-tool.component.html @@ -6,8 +6,8 @@ <mat-icon class="fas fa-search"></mat-icon> </button> </mat-form-field> - <mat-accordion [class.criteria-container]="!adminMode" [class.criteria-container-admin]="adminMode"> - <mat-expansion-panel expanded #criteriaTool [expanded]="false" togglePosition="before"> + <mat-accordion [class.criteria-container]="class==='main'" [class.criteria-container-secondary]="class==='secondary'"> + <mat-expansion-panel #criteriaTool [expanded]="openedPanel" togglePosition="before"> <mat-expansion-panel-header> <mat-panel-title> <span> diff --git a/src/frontend/app/adv-search/criteria-tool/criteria-tool.component.scss b/src/frontend/app/adv-search/criteria-tool/criteria-tool.component.scss index 0f9e5b44abb262824394a4268023ac20c1c86f39..3869e0539d4588753a625d7c62cb0b26533db26c 100644 --- a/src/frontend/app/adv-search/criteria-tool/criteria-tool.component.scss +++ b/src/frontend/app/adv-search/criteria-tool/criteria-tool.component.scss @@ -95,7 +95,7 @@ } } -.criteria-container-admin { +.criteria-container-secondary { .mat-expansion-panel-header-title { color: $primary; align-items: center; diff --git a/src/frontend/app/adv-search/criteria-tool/criteria-tool.component.ts b/src/frontend/app/adv-search/criteria-tool/criteria-tool.component.ts index d26408adf7255720bf521f92abf9bb4909136af4..930670d5f7aa0d18f0999ae9244926a7af5a0297 100644 --- a/src/frontend/app/adv-search/criteria-tool/criteria-tool.component.ts +++ b/src/frontend/app/adv-search/criteria-tool/criteria-tool.component.ts @@ -5,7 +5,7 @@ import { AppService } from '@service/app.service'; import { FunctionsService } from '@service/functions.service'; import { Observable, of } from 'rxjs'; import { FormControl } from '@angular/forms'; -import { startWith, map, tap, filter, exhaustMap, catchError, elementAt } from 'rxjs/operators'; +import { startWith, map, tap, filter, exhaustMap, catchError } from 'rxjs/operators'; import { LatinisePipe } from 'ngx-pipes'; import { MatExpansionPanel } from '@angular/material/expansion'; import { IndexingFieldsService } from '@service/indexing-fields.service'; @@ -59,6 +59,8 @@ export class CriteriaToolComponent implements OnInit { @Input() searchTerm: string = 'Foo'; @Input() defaultCriteria: any = []; @Input() adminMode: boolean = false; + @Input() openedPanel: boolean = false; + @Input() class: 'main' | 'secondary' = 'main'; @Output() searchUrlGenerated = new EventEmitter<any>(); @Output() loaded = new EventEmitter<any>(); @@ -126,8 +128,6 @@ export class CriteriaToolComponent implements OnInit { if (!this.adminMode) { this.getSearchTemplates(); - } else { - this.toggleTool(true); } } @@ -594,7 +594,7 @@ export class CriteriaToolComponent implements OnInit { query.push({ 'identifier': field.identifier, 'values': field.control.value }); }); - query.push({ 'identifier': 'searchTerm', 'values': this.searchTermControl.value }); + query.push({ 'identifier': 'meta', 'values': this.searchTermControl.value }); const dialogRef = this.dialog.open( AddSearchTemplateModalComponent, @@ -649,21 +649,20 @@ export class CriteriaToolComponent implements OnInit { } selectSearchTemplate(searchTemplate: any, openPanel: boolean = true) { - this.currentCriteria = []; + let index: number; this.criteria.forEach((element: any) => { - let index = searchTemplate.query.map((field: any) => field.identifier).indexOf(element.identifier); + index = searchTemplate.query.map((field: any) => field.identifier).indexOf(element.identifier); if (index > -1) { - element.control = new FormControl({ value: searchTemplate.query[index].values, disabled: false }); - element.control.value = searchTemplate.query[index].values; + if (element.control === undefined) { + element.control = new FormControl({ value: searchTemplate.query[index].values, disabled: false }); + } + element.control.setValue(searchTemplate.query[index].values); this.addCriteria(element, openPanel); if (element.type === 'selectAutocomplete') { setTimeout(() => { - console.log(element.identifier); - console.log(this.pluginSelectAutocompleteSearch.toArray()); - this.pluginSelectAutocompleteSearch.toArray().filter((component: any) => component.id === element.identifier)[0].setDatas(element.control.value); this.pluginSelectAutocompleteSearch.toArray().filter((component: any) => component.id === element.identifier)[0].resetACDatas(); }, 0); @@ -671,7 +670,7 @@ export class CriteriaToolComponent implements OnInit { } }); - let index = searchTemplate.query.map((field: any) => field.identifier).indexOf('searchTerm'); + index = searchTemplate.query.map((field: any) => field.identifier).indexOf('meta'); if (index > -1) { this.searchTermControl.setValue(searchTemplate.query[index].values); } diff --git a/src/frontend/app/adv-search/list/search-adv-list.component.html b/src/frontend/app/adv-search/list/search-adv-list.component.html deleted file mode 100755 index 44c3c803311830e6231e4e2fdb3f77f2541eb1eb..0000000000000000000000000000000000000000 --- a/src/frontend/app/adv-search/list/search-adv-list.component.html +++ /dev/null @@ -1,75 +0,0 @@ -<div class="example-loading-shade" *ngIf="isLoadingResults"> - <mat-spinner *ngIf="isLoadingResults"></mat-spinner> -</div> -<div style="display: flex;"> - <div style="display: flex;align-items: center;"> - <b>{{allResInSearch.length}} - {{'lang.records' | translate | ucfirst}}</b> <small *ngIf="selectedRes.length > 0" style="color: #029BB6;">- {{selectedRes.length}} - {{'lang.selected' | translate}}</small> - </div> - <div style="flex:1;"> - <mat-paginator #paginatorResultList [length]="resultsLength" [hidePageSize]="true" [pageSize]="10" - class="paginatorResultList"></mat-paginator> - </div> -</div> -<div style="overflow: auto;"> - <mat-table cdkDropList id="document-list" #tableResourceListSort="matSort" [dataSource]="data" matSort - matSortActive="creationDate" matSortDisableClear matSortDirection="desc" style="width:100%;min-width: 800px;"> - - <ng-container matColumnDef="action"> - <mat-header-cell *matHeaderCellDef style="width: 70px;flex: initial;"> - <mat-checkbox color="primary" - *ngIf="!singleMode" - [checked]="selectedRes.length == allResInSearch.length && selectedRes.length > 0" - [indeterminate]="selectedRes.length > 0 && selectedRes.length < allResInSearch.length" - title="{{'lang.selectAllResInBasket' | translate}}" (change)="toggleAllRes($event)"></mat-checkbox> - </mat-header-cell> - <mat-cell *matCellDef="let row" style="width: 70px;flex: initial;"> - <mat-checkbox color="primary" [checked]="this.selectedRes.indexOf(row.resId) !== -1" (change)="toggleRes($event,row)" - (click)="$event.stopPropagation();" [disabled]="excludeRes.indexOf(row.resId) > -1"> - </mat-checkbox> - </mat-cell> - </ng-container> - <ng-container matColumnDef="category"> - <mat-header-cell *matHeaderCellDef mat-sort-header> {{'lang.category_id' | translate}} </mat-header-cell> - <mat-cell *matCellDef="let row"> - {{'lang.' + row.category | translate}} - </mat-cell> - </ng-container> - - <ng-container matColumnDef="chrono"> - <mat-header-cell *matHeaderCellDef mat-sort-header> {{'lang.chrono' | translate}} </mat-header-cell> - <mat-cell *matCellDef="let row"> - {{row.chrono}} - </mat-cell> - </ng-container> - <ng-container matColumnDef="status"> - <mat-header-cell *matHeaderCellDef mat-sort-header style="width: 100px;flex: initial"> {{'lang.status' | translate}} </mat-header-cell> - <mat-cell *matCellDef="let row" style="width: 100px;flex: initial"> - <mat-icon title="{{row.statusLabel}}" [ngStyle]="{'color': row.priorityColor}" color="primary" - class="{{row.statusImage.charAt(0)}}{{row.statusImage.charAt(1)}} {{row.statusImage}} {{row.statusImage.charAt(0)}}{{row.statusImage.charAt(1)}}-2x"> - </mat-icon> - </mat-cell> - </ng-container> - <ng-container matColumnDef="subject"> - <mat-header-cell *matHeaderCellDef mat-sort-header style="flex:2"> {{'lang.subject' | translate}} </mat-header-cell> - <mat-cell *matCellDef="let row" style="flex:2"> - {{row.subject}} - </mat-cell> - </ng-container> - <ng-container matColumnDef="typeLabel"> - <mat-header-cell *matHeaderCellDef mat-sort-header> {{'lang.doctype' | translate}} </mat-header-cell> - <mat-cell *matCellDef="let row"> - {{row.typeLabel}} - </mat-cell> - </ng-container> - <ng-container matColumnDef="creationDate"> - <mat-header-cell *matHeaderCellDef mat-sort-header> {{'lang.creationDate' | translate}} </mat-header-cell> - <mat-cell *matCellDef="let row"> - {{row.creationDate |Â timeAgo : 'full' | ucfirst}} - </mat-cell> - </ng-container> - <mat-header-row *matHeaderRowDef="displayedColumnsResource"></mat-header-row> - <mat-row *matRowDef="let row; columns: displayedColumnsResource;" class="rowData"></mat-row> - </mat-table> -</div> diff --git a/src/frontend/app/adv-search/list/search-adv-list.component.scss b/src/frontend/app/adv-search/list/search-adv-list.component.scss deleted file mode 100644 index a0d9a66e2f1cb6d043c539664cc8f8d0d96f99db..0000000000000000000000000000000000000000 --- a/src/frontend/app/adv-search/list/search-adv-list.component.scss +++ /dev/null @@ -1,85 +0,0 @@ -@import '../../../css/vars.scss'; - -.align_leftData { - text-align: left; -} - -.align_centerData { - text-align: center; -} - -.align_rightData { - text-align: right; -} - -.boldFontData { - font-weight: bold; -} - -.bigFontData { - font-size: 14px; -} - -.smallFontData { - font-size: 10px; -} - -.normalData { - flex: 1; -} - -.chronoData { - width: 150px; -} - -.longData { - flex: 3; -} - -.locked { - opacity: 0.5; -} - -.watermark { - position: absolute; - left: 50%; - transform: translateX(-50%) rotate(-20deg); - color: red; - font-weight: bold; - opacity: 0.6; -} - -.dragPreview { - text-align: center; - border-radius: 5px; - background: white; - padding: 10px; - box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2), 0 8px 10px 1px rgba(0, 0, 0, 0.14), 0 3px 14px 2px rgba(0, 0, 0, 0.12); - color: $primary; -} - -.folder-info { - padding-left: 20px; - padding-right: 20px; -} - -.badge-folder { - cursor: pointer; - background: $secondary; - margin: 5px; - font-size: 12px; - border-radius: 3px; - opacity: 0.8; -} - -.hasEvent { - cursor: pointer; - - &:hover { - color: $primary; - } -} - -.followIcon { - color: $secondary; -} diff --git a/src/frontend/app/adv-search/list/search-adv-list.component.ts b/src/frontend/app/adv-search/list/search-adv-list.component.ts deleted file mode 100644 index 30bd286a4e3c4a0484bb5d915029112da9922f46..0000000000000000000000000000000000000000 --- a/src/frontend/app/adv-search/list/search-adv-list.component.ts +++ /dev/null @@ -1,176 +0,0 @@ -import { Component, OnInit, ViewChild, EventEmitter, Input } from '@angular/core'; -import { HttpClient } from '@angular/common/http'; -import { TranslateService } from '@ngx-translate/core'; -import { NotificationService } from '@service/notification/notification.service'; -import { HeaderService } from '@service/header.service'; -import { AppService } from '@service/app.service'; -import { Observable, merge, Subject, of as observableOf, of } from 'rxjs'; -import { MatDialog } from '@angular/material/dialog'; -import { MatPaginator } from '@angular/material/paginator'; -import { MatSort } from '@angular/material/sort'; -import { takeUntil, startWith, switchMap, map, catchError, filter, exhaustMap, tap, debounceTime, distinctUntilChanged } from 'rxjs/operators'; -import { FormControl } from '@angular/forms'; -import { FunctionsService } from '@service/functions.service'; - -@Component({ - selector: 'search-adv-list', - templateUrl: "search-adv-list.component.html", - styleUrls: ['search-adv-list.component.scss'] -}) -export class SearchAdvListComponent implements OnInit { - - - loading: boolean = false; - - filtersChange = new EventEmitter(); - - data: any; - - displayedColumnsResource: string[] = ['action', 'category', 'chrono', 'status', 'subject', 'typeLabel', 'creationDate']; - - selectedRes: number[] = []; - allResInSearch: number[] = []; - - isLoadingResults = true; - routeUrl: string = '../rest/search'; - resultListDatabase: ResourceListHttpDao | null; - resultsLength = 0; - - searchResource = new FormControl(); - - @Input('search') search: string = ''; - @Input('singleMode') singleMode: boolean = false; - @Input('excludeRes') excludeRes: number[] = []; - - - - @ViewChild(MatPaginator, { static: true }) paginator: MatPaginator; - @ViewChild('tableResourceListSort', { static: true }) sort: MatSort; - - private destroy$ = new Subject<boolean>(); - - constructor( - public translate: TranslateService, - public http: HttpClient, - private notify: NotificationService, - private headerService: HeaderService, - public appService: AppService, - public dialog: MatDialog, - public functions: FunctionsService) { } - - ngOnInit(): void { - this.loading = true; - this.initResourceList(); - this.selectedRes = []; - } - - initResourceList() { - this.resultListDatabase = new ResourceListHttpDao(this.http); - this.paginator.pageIndex = 0; - this.sort.active = 'creationDate'; - this.sort.direction = 'desc'; - this.sort.sortChange.subscribe(() => this.paginator.pageIndex = 0); - - // When list is refresh (sort, page, filters) - merge(this.sort.sortChange, this.paginator.page, this.filtersChange) - .pipe( - takeUntil(this.destroy$), - startWith({}), - switchMap(() => { - this.isLoadingResults = true; - return this.resultListDatabase!.getRepoIssues( - this.sort.active, this.sort.direction, this.paginator.pageIndex, this.routeUrl, this.search); - }), - map(data => { - this.isLoadingResults = false; - data = this.processPostData(data); - this.resultsLength = data.count; - this.allResInSearch = data.allResources; - return data.resources; - }), - catchError((err: any) => { - this.notify.handleErrors(err); - this.isLoadingResults = false; - return observableOf([]); - }) - ).subscribe(data => this.data = data); - } - - processPostData(data: any) { - - data.resources.forEach((linkeRes: any) => { - Object.keys(linkeRes).forEach((key) => { - if (key == 'statusImage' && this.functions.empty(linkeRes[key])) { - linkeRes[key] = 'fa-question undefined'; - } else if (this.functions.empty(linkeRes[key]) && ['senders', 'recipients', 'attachments', 'hasDocument'].indexOf(key) === -1) { - linkeRes[key] = this.translate.instant('lang.undefined'); - } - }); - }); - - return data; - } - - refreshDao(newUrl: string = null) { - if (newUrl !== null) { - this.search = newUrl; - } - this.filtersChange.emit(); - } - - toggleRes(e: any, row: any) { - if (this.singleMode) { - this.selectedRes = []; - } - if (e.checked) { - if (this.selectedRes.indexOf(row.resId) === -1) { - this.selectedRes.push(row.resId); - row.checked = true; - } - } else { - let index = this.selectedRes.indexOf(row.resId); - this.selectedRes.splice(index, 1); - row.checked = false; - } - } - - toggleAllRes(e: any) { - this.selectedRes = []; - if (e.checked) { - this.data.forEach((element: any) => { - if (this.excludeRes.indexOf(element['resId']) === -1) { - element['checked'] = true; - } - }); - let selectResEnabled = this.allResInSearch.filter(elem => this.excludeRes.indexOf(elem) === -1) - this.selectedRes = JSON.parse(JSON.stringify(selectResEnabled)); - } else { - this.data.forEach((element: any) => { - element['checked'] = false; - }); - } - } - - getSelectedRessources() { - return this.selectedRes; - } - -} - -export interface ResourceList { - resources: any[]; - count: number; - allResources : number[] -} -export class ResourceListHttpDao { - - constructor(private http: HttpClient) { } - - getRepoIssues(sort: string, order: string, page: number, href: string, search: string): Observable<ResourceList> { - - let offset = page * 10; - const requestUrl = `${href}?limit=10&offset=${offset}&order=${order}&orderBy=${sort}${search}`; - - return this.http.get<ResourceList>(requestUrl); - } -} diff --git a/src/frontend/app/adv-search/result-list/search-result-list.component.html b/src/frontend/app/adv-search/result-list/search-result-list.component.html new file mode 100644 index 0000000000000000000000000000000000000000..0b4a47f05cd7172dcb97745186aa1f1b5af08e96 --- /dev/null +++ b/src/frontend/app/adv-search/result-list/search-result-list.component.html @@ -0,0 +1,224 @@ +<mat-card id="viewThumbnail" style="display:none;position: fixed;z-index: 2;margin-left: 1px;"> + <img style="max-height: 100vh;" [src]="thumbnailUrl | secureUrl | async" /> +</mat-card> +<ng-template #filterTemplate> + <app-filter-tool-adv-search *ngIf="!hideFilter && data.length > 0"></app-filter-tool-adv-search> +</ng-template> +<ng-template #toolTemplate> + <div *ngIf="initSearch" class="filtersContent"> + <div style="flex: 1"></div> + <div class="orderTool"> + <mat-form-field class="basket-order"> + <mat-icon matPrefix class="fa fa-list"></mat-icon> + <mat-select [(ngModel)]="this.listProperties.order" (selectionChange)="updateFilters()"> + <mat-option [value]="column.id" *ngFor="let column of displayColsOrder"> + {{'lang.' + column.id | translate}} + </mat-option> + </mat-select> + </mat-form-field> + </div> + <div class="ascDescTool"> + <button [disabled]="this.listProperties.order == ''" + [style.opacity]="this.listProperties.order == '' ? '0.2' : '1'" mat-fab + [title]="this.listProperties.orderDir == 'DESC' ? this.translate.instant('lang.descOrder') : this.translate.instant('lang.ascOrder')" + style="color: rgba(0,0,0,0.38);" (click)="changeOrderDir();"> + <mat-icon *ngIf="this.listProperties.orderDir == 'DESC'" fontSet="fas" + fontIcon="fa-sort-amount-down fa-2x"> + </mat-icon> + <mat-icon *ngIf="this.listProperties.orderDir == 'ASC'" fontSet="fas" + fontIcon="fa-sort-amount-up fa-2x"> + </mat-icon> + </button> + </div> + </div> + <div class="filterBadges"> + <span *ngIf="!emptyCriteria()" class="label badge-eraser" (click)="removeCriteria('_ALL')" + title="{{'lang.eraseAllFilters' | translate}}"><i class="fas fa-eraser"></i></span> + <ng-container *ngFor="let critKey of criteria | keyvalue"> + <ng-container *ngIf="isArrayType(critKey.value.values) && critKey.value.values.length <= 3"> + <span class="label badge-search" *ngFor="let val of critKey.value.values" + [title]="appCriteriaTool.getLabelValue(critKey.key,val)" + (click)="removeCriteria(critKey.key, val)"><i + class="fa {{indexingFieldService.getField(critKey.key).icon}}" + [title]="indexingFieldService.getField(critKey.key).label"></i> {{appCriteriaTool.getLabelValue(critKey.key,val)}} <i + class="fa fa-times-circle"></i></span> + </ng-container> + <ng-container *ngIf="isArrayType(critKey.value.values) && critKey.value.values.length > 3"> + <span class="label badge-search" + [title]="appCriteriaTool.getLabelValues(critKey.key,critKey.value.values)" + (click)="removeCriteria(critKey.key)"><i + class="fa {{indexingFieldService.getField(critKey.key).icon}}" + [title]="indexingFieldService.getField(critKey.key).label"></i> {{critKey.value.values.length}} + valeurs <i class="fa fa-times-circle"></i></span> + </ng-container> + <ng-container *ngIf="!isArrayType(critKey.value.values) && critKey.key !== 'meta'"> + <span class="label badge-search" + [title]="appCriteriaTool.getFormatLabel(critKey.key,critKey.value.values)" + (click)="removeCriteria(critKey.key)"><i + class="fa {{indexingFieldService.getField(critKey.key).icon}}" + [title]="indexingFieldService.getField(critKey.key).label"></i> {{appCriteriaTool.getFormatLabel(critKey.key,critKey.value.values)}} <i + class="fa fa-times-circle"></i></span> + </ng-container> + <ng-container *ngIf="!isArrayType(critKey.value.values) && critKey.key === 'meta'"> + <span class="label badge-search" [title]="'meta'" + (click)="removeCriteria(critKey.key)">{{critKey.value.values}} <i + class="fa fa-times-circle"></i></span> + </ng-container> + </ng-container> + </div> +</ng-template> +<div class="example-loading-shade" *ngIf="isLoadingResults"> + <mat-spinner *ngIf="isLoadingResults"></mat-spinner> +</div> +<div class="table-head"> + <div class="table-head-result"> + <mat-checkbox *ngIf="!singleSelection" color="primary" [checked]="selectedRes.length == allResInBasket.length && selectedRes.length > 0" + [indeterminate]="selectedRes.length > 0 && selectedRes.length < allResInBasket.length" + style="margin: 10px;padding-right: 10px;" title="{{'lang.selectAllResInBasket' | translate}}" + (change)="toggleAllRes($event)"> + </mat-checkbox> {{resultsLength}} + {{'lang.records' | translate | ucfirst}} <small *ngIf="selectedRes.length > 0">- + {{selectedRes.length}} + {{'lang.selected' | translate}}</small> + </div> + <div class="table-head-tool"> + <span> + <mat-paginator #paginatorResultList [length]="resultsLength" [pageSizeOptions]="[10, 25, 50, 100, 150]" + class="paginatorResultList"></mat-paginator> + </span> + <span> + <span> + <app-tools-list #actionsList [selectedRes]="selectedRes"></app-tools-list> + </span> + <span *ngIf="actionMode"> + <app-followed-action-list #actionsList [contextMode]="false" [currentFolderInfo]="folderInfo" + [totalRes]="allResInBasket.length" [selectedRes]="selectedRes" + (refreshEvent)="refreshDaoAfterAction()"> + </app-followed-action-list> + </span> + </span> + </div> +</div> +<div [class.integratedContent]="!standalone"> + <table #tableBasketListSort="matSort" cdkDropList id="folder-list" [cdkDropListConnectedTo]="listTodrag()" + [cdkDropListData]="data" [cdkDropListDisabled]="dragInit || appService.getViewMode()" mat-table + [dataSource]="data" matSort matSortActive="resId" matSortDisableClear matSortDirection="asc" + style="width:100%;"> + + <ng-container matColumnDef="resId"> + <td mat-cell *matCellDef="let row" style="padding:0;border-top: solid 1px rgba(0, 0, 0, 0.12);" + [class.selected-data]="row.checked"> + <div class="main-info"> + <span style="width:50px;"> + <mat-checkbox color="primary" [checked]="row.checked" (change)="toggleRes($event,row)" + (click)="$event.stopPropagation();"> + </mat-checkbox> + </span> + <button mat-icon-button (click)="$event.stopPropagation();toggleMailTracking(row)" + style="margin-left: -25px;" class="followIcon" + [title]="row.mailTracking === true ? this.translate.instant('lang.untrackThisMail') : this.translate.instant('lang.trackThisMail')"> + <mat-icon [ngClass]="[row.mailTracking === true ? 'fas fa-star' : 'far fa-star']" + style="margin-bottom: 5px;"></mat-icon> + </button> + <span style="cursor:pointer;" class="main-info-status" (click)="launch(row);"> + <mat-icon *ngIf="row.isLocked !== true" title="{{row.statusLabel}}" + [ngStyle]="{'color': row.priorityColor}" style="width: 100%;" color="primary" + class="{{row.statusImage.charAt(0)}}{{row.statusImage.charAt(1)}} {{row.statusImage}} {{row.statusImage.charAt(0)}}{{row.statusImage.charAt(1)}}-2x"> + </mat-icon> + <span *ngIf="row.confidentiality === 'Y'" + class="watermark">{{'lang.confidential' | translate}}</span> + <mat-icon *ngIf="row.isLocked === true" + title="{{'lang.warnLockResInProgress' | translate}} : {{row.locker}}" style="color: red;" + class="fa fa-lock fa-2x"> + </mat-icon> + </span> + <span *ngIf="!appService.getViewMode()" (click)="launch(row);" class="main-info-data" + style="width:200px;text-align:center;cursor:pointer;"> + <ng-container + *ngIf="row.chrono == this.translate.instant('lang.undefined') && row.barcode != this.translate.instant('lang.undefined')"> + <span style="color: rgba(0,0,0,0.4);font-size: 90%;"> + <i title="{{'lang.barcode' | translate}}" class="fas fa-barcode"></i> + {{row.barcode}}</span> + </ng-container> + <ng-container *ngIf="row.chrono != this.translate.instant('lang.undefined')"> + {{row.chrono}} + </ng-container> + </span> + <span class="main-info-data" (click)="launch(row);" style="font-weight:bold;flex:1;cursor:pointer;" + [class.undefined]="row.subject == this.translate.instant('lang.undefined')" + title="{{row.subject}}">{{row.subject | shorten: 150: '...'}}</span> + <span *ngIf="sidenavRight !== undefined" class="main-info-action"> + <button mat-icon-button title="{{'lang.notes' | translate}}" + (click)="$event.stopPropagation();togglePanel('note',row)" + [class.noData]="row.countNotes == 0"> + <mat-icon matBadgeHidden="{{row.countNotes == 0}}" fontSet="fas" + matBadge="{{row.countNotes}}" fontIcon="fa-comments fa-2x" + [color]="sidenavRight.opened && row.checked && currentMode == 'note' ? 'primary' : ''"> + </mat-icon> + </button> + <button mat-icon-button title="{{'lang.attachments' | translate}}" + (click)="$event.stopPropagation();togglePanel('attachment',row)" + [class.noData]="row.countAttachments == 0"> + <mat-icon matBadgeHidden="{{row.countAttachments == 0}}" fontSet="fas" + matBadge="{{row.countAttachments}}" fontIcon="fa-paperclip fa-2x" + [color]="sidenavRight.opened && row.checked && currentMode == 'attachment' ? 'primary' : ''"> + </mat-icon> + </button> + <button mat-icon-button title="{{'lang.diffusionList' | translate}}" + (click)="$event.stopPropagation();togglePanel('diffusion',row)"> + <mat-icon fontSet="fas" fontIcon="fa-sitemap fa-2x" + [color]="sidenavRight.opened && row.checked && currentMode == 'diffusion' ? 'primary' : ''"> + </mat-icon> + </button> + <button mat-icon-button *ngIf="!appService.getViewMode()" + title="{{row.hasDocument ? this.translate.instant('lang.viewResource') : this.translate.instant('lang.noDocument')}}" + (click)="$event.stopPropagation();viewDocument(row)" (mouseenter)="viewThumbnail(row);" + (mouseleave)="closeThumbnail();" [disabled]="!row.hasDocument"> + <mat-icon class="fa" [ngClass]="[row.hasDocument ? 'fa-eye' : 'fa-eye-slash']"> + </mat-icon> + </button> + <button mat-icon-button *ngIf="!appService.getViewMode()" + title="{{'lang.linkDetails' | translate}}" + (click)="$event.stopPropagation();goToDetail(row);"> + <mat-icon fontSet="fas" fontIcon="fa-info-circle fa-2x"></mat-icon> + </button> + </span> + </div> + <div *ngIf="row.folders !== undefined && row.folders.length > 0" class="folder-info"> + <span class="badge badge-folder" *ngFor="let folder of row.folders | sortBy : 'label'" + (click)="$event.stopPropagation();goToFolder(folder);" + title="{{'lang.goToFolder' | translate}} : {{folder.label}}"><i class="fa fa-folder"></i> + {{folder.label}}</span> + </div> + </td> + </ng-container> + <tr mat-row *matRowDef="let row; columns: displayedColumnsBasket;" (contextmenu)="open($event,row);" + class="rowData" style="cursor: pointer;" [class.locked]="row.isLocked == true" cdkDrag + [cdkDragDisabled]="!row.allowed" (cdkDragStarted)="selectSpecificRes(row);" [cdkDragData]="row"> + <div class="example-custom-placeholder" *cdkDragPlaceholder></div> + <div class="dragPreview" *cdkDragPreview><i class="fas fa-envelope-open-text fa-2x"></i> + <br /> + {{'lang.classifyInFolder' | translate}} : + <b>{{row.chrono}}</b> + </div> + </tr> + </table> +</div> +<div class="table-head"> +</div> +<ng-template #panelTemplate> + <div *ngIf="innerHtml" [matTooltip]="currentChrono" [innerHTML]="innerHtml" style="height: 100%;overflow: hidden;"> + </div> + <div style="display:flex;position: sticky;top: 0px;z-index: 2;"> + <button mat-icon-button (click)="sidenavRight.close()" style="font-size: 20px;color:#666;"> + <mat-icon class="fa fa-arrow-right"></mat-icon> + </button> + </div> + <app-panel-list #appPanelList (refreshBadgeNotes)="refreshBadgeNotes($event)" + (refreshBadgeAttachments)="refreshBadgeAttachments($event)"></app-panel-list> + <mat-divider></mat-divider> +</ng-template> +<app-followed-action-list #actionsListContext *ngIf="actionMode" [contextMode]="true" [currentFolderInfo]="folderInfo" + [totalRes]="allResInBasket.length" [selectedRes]="selectedRes" (refreshEvent)="refreshDaoAfterAction()" + (refreshPanelFolders)="foldersService.getFolders()"> +</app-followed-action-list> \ No newline at end of file diff --git a/src/frontend/app/adv-search/result-list/search-result-list.component.scss b/src/frontend/app/adv-search/result-list/search-result-list.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..8ff8f6a7047360ade231a72d3207533c5643ad0f --- /dev/null +++ b/src/frontend/app/adv-search/result-list/search-result-list.component.scss @@ -0,0 +1,119 @@ +@import '../../../css/vars.scss'; + + +.followIcon { + color: $secondary; +} + +.folder-info { + padding-left: 20px; + padding-right: 20px; +} + +.badge-folder { + cursor: pointer; + background: $secondary; + margin: 5px; + font-size: 12px; + border-radius: 3px; + opacity: 0.8; + max-width: 250px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.filterBadges { + width: 100%; +} +.filterBadges>.badge-eraser { + margin: 5px; + background: none; + cursor: pointer; + color: white; + font-size: 20px; +} + +.filterBadges>.label { + white-space: inherit; +} + +.filterBadges>.badge-search { + margin: 5px; + background: white; + color: $primary; + cursor: pointer; +} + +.filtersContent { + width: 100%; + display: flex; + + .orderTool { + flex: 1; + } +} + + +.basket-order { + color: white; + width: 300px; + padding-left: 10px; + padding-right: 10px; + height: 55px; + + .mat-icon { + font-size: 30px; + + } + + ::ng-deep.mat-select-value { + color: white; + } + + ::ng-deep.mat-form-field-infix { + padding-bottom: 15px; + } + + ::ng-deep.mat-form-field-flex { + background: #135F7F; + border: solid 2px white; + border-radius: 30px; + display: flex; + align-items: center; + } + + ::ng-deep.mat-form-field-prefix { + padding-left: 15px; + padding-right: 10px; + } + + ::ng-deep.mat-form-field-label-wrapper { + color: white; + } + + ::ng-deep.mat-select-arrow { + color: white; + margin-right: 25px; + } + + ::ng-deep .mat-form-field-underline { + display: none; + } +} + +.ascDescTool { + .mat-fab { + background: #135F7F; + border: solid 2px white; + color: white !important; + box-shadow: none; + } +} + +.integratedContent { + height:90%; + overflow:auto; + position:absolute; + width:100%; +} \ No newline at end of file diff --git a/src/frontend/app/adv-search/result-list/search-result-list.component.ts b/src/frontend/app/adv-search/result-list/search-result-list.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..4655b90a1d438a4b6ac5133778bb39fa944b2ad7 --- /dev/null +++ b/src/frontend/app/adv-search/result-list/search-result-list.component.ts @@ -0,0 +1,552 @@ +import { Component, OnInit, ViewChild, EventEmitter, ViewContainerRef, OnDestroy, TemplateRef, Input } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { TranslateService } from '@ngx-translate/core'; +import { NotificationService } from '@service/notification/notification.service'; +import { MatDialog, MatDialogRef } from '@angular/material/dialog'; +import { MatPaginator } from '@angular/material/paginator'; +import { MatSidenav } from '@angular/material/sidenav'; +import { MatSort } from '@angular/material/sort'; +import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; +import { startWith, switchMap, map, catchError, takeUntil, tap } from 'rxjs/operators'; +import { ActivatedRoute, Router } from '@angular/router'; +import { HeaderService } from '@service/header.service'; +import { Overlay } from '@angular/cdk/overlay'; +import { PanelListComponent } from '@appRoot/list/panel/panel-list.component'; +import { AppService } from '@service/app.service'; +import { BasketHomeComponent } from '@appRoot/basket/basket-home.component'; +import { FolderActionListComponent } from '@appRoot/folder/folder-action-list/folder-action-list.component'; +import { FoldersService } from '@appRoot/folder/folders.service'; +import { FunctionsService } from '@service/functions.service'; +import { of, merge, Subject, Subscription, Observable } from 'rxjs'; +import { CriteriaToolComponent } from '@appRoot/adv-search/criteria-tool/criteria-tool.component'; +import { IndexingFieldsService } from '@service/indexing-fields.service'; +import { CriteriaSearchService } from '@service/criteriaSearch.service'; + +declare var $: any; + +@Component({ + selector: 'app-search-result-list', + templateUrl: 'search-result-list.component.html', + styleUrls: ['search-result-list.component.scss'] +}) +export class SearchResultListComponent implements OnInit, OnDestroy { + + @Input() searchTerm: string = ''; + @Input() actionMode: boolean = true; + @Input() singleSelection: boolean = false; + @Input() standalone: boolean = false; + @Input() hideFilter: boolean = false; + @Input() appCriteriaTool: CriteriaToolComponent; + @Input() sidenavRight: MatSidenav; + + loading: boolean = true; + initSearch: boolean = false; + docUrl: string = ''; + public innerHtml: SafeHtml; + searchUrl: string = '../rest/search'; + // searchTerm: string = ''; + criteria: any = {}; + homeData: any; + + injectDatasParam = { + resId: 0, + editable: false + }; + currentResource: any = {}; + + filtersChange = new EventEmitter(); + + dragInit: boolean = true; + + dialogRef: MatDialogRef<any>; + + displayedColumnsBasket: string[] = ['resId']; + + displayedMainData: any = [ + { + 'value': 'chrono', + 'cssClasses': ['softColorData', 'align_centerData', 'chronoData'], + 'icon': '' + }, + { + 'value': 'subject', + 'cssClasses': ['longData'], + 'icon': '' + } + ]; + + resultListDatabase: ResultListHttpDao | null; + data: any = []; + resultsLength = 0; + isLoadingResults = true; + listProperties: any = {}; + currentChrono: string = ''; + currentMode: string = ''; + + thumbnailUrl: string = ''; + + selectedRes: Array<number> = []; + allResInBasket: number[] = []; + selectedDiffusionTab: number = 0; + folderInfo: any = { + id: 0, + 'label': '', + 'ownerDisplayName': '', + 'entitiesSharing': [] + }; + folderInfoOpened: boolean = false; + + private destroy$ = new Subject<boolean>(); + subscription: Subscription; + + displayColsOrder = [ + { 'id': 'destUser' }, + { 'id': 'categoryId' }, + { 'id': 'creationDate' }, + { 'id': 'processLimitDate' }, + { 'id': 'entityLabel' }, + { 'id': 'subject' }, + { 'id': 'chrono' }, + { 'id': 'priority' }, + { 'id': 'status' }, + { 'id': 'typeLabel' } + ]; + + @ViewChild('filterTemplate', { static: true }) filterTemplate: TemplateRef<any>; + @ViewChild('toolTemplate', { static: true }) toolTemplate: TemplateRef<any>; + @ViewChild('panelTemplate', { static: true }) panelTemplate: TemplateRef<any>; + @ViewChild('adminMenuTemplate', { static: true }) adminMenuTemplate: TemplateRef<any>; + @ViewChild('actionsListContext', { static: false }) actionsList: FolderActionListComponent; + @ViewChild('appPanelList', { static: false }) appPanelList: PanelListComponent; + // @ViewChild('appCriteriaTool', { static: true }) appCriteriaTool: CriteriaToolComponent; + + currentSelectedChrono: string = ''; + + @ViewChild(MatPaginator, { static: true }) paginator: MatPaginator; + @ViewChild('tableBasketListSort', { static: true }) sort: MatSort; + @ViewChild('basketHome', { static: true }) basketHome: BasketHomeComponent; + + constructor( + private _activatedRoute: ActivatedRoute, + public translate: TranslateService, + private router: Router, + private route: ActivatedRoute, + public http: HttpClient, + public dialog: MatDialog, + private sanitizer: DomSanitizer, + private headerService: HeaderService, + public criteriaSearchService: CriteriaSearchService, + private notify: NotificationService, + public overlay: Overlay, + public viewContainerRef: ViewContainerRef, + public appService: AppService, + public foldersService: FoldersService, + public functions: FunctionsService, + public indexingFieldService: IndexingFieldsService) { + _activatedRoute.queryParams.subscribe( + params => { + if (!this.functions.empty(params.value)) { + this.searchTerm = params.value; + this.initSearch = true; + this.criteria = { + meta: { + values: this.searchTerm + } + }; + } + } + ); + } + + ngOnInit(): void { + if (!this.functions.empty(this.searchTerm)) { + this.initSearch = true; + this.criteria = { + meta: { + values: this.searchTerm + } + }; + } + this.headerService.sideBarAdmin = true; + + this.isLoadingResults = false; + + if (this.toolTemplate !== undefined) { + this.headerService.initTemplate(this.toolTemplate, this.viewContainerRef, 'toolTemplate'); + } + + if (this.panelTemplate !== undefined && this.sidenavRight !== undefined) { + this.headerService.initTemplate(this.panelTemplate, this.viewContainerRef, 'panelTemplate'); + } + + if (this.filterTemplate !== undefined && !this.hideFilter) { + this.headerService.initTemplate(this.filterTemplate, this.viewContainerRef, 'filterTemplate'); + } + + this.listProperties = this.criteriaSearchService.initListsProperties(this.headerService.user.id); + + this.loading = false; + } + + initSavedCriteria() { + if (Object.keys(this.listProperties.criteria).length > 0) { + const obj = { query: [] }; + Object.keys(this.listProperties.criteria).forEach(key => { + const objectItem = {}; + objectItem['identifier'] = key; + objectItem['values'] = this.listProperties.criteria[key].values; + obj.query.push(objectItem); + }); + this.appCriteriaTool.selectSearchTemplate(obj, false); + this.criteria = this.listProperties.criteria; + this.initResultList(); + } else if (this.initSearch) { + this.initResultList(); + } else { + this.appCriteriaTool.toggleTool(true); + } + } + + ngOnDestroy() { + this.destroy$.next(true); + } + + launch(row: any) { + const thisSelect = { checked: true }; + const thisDeselect = { checked: false }; + + if (this.actionMode) { + row.checked = true; + this.toggleAllRes(thisDeselect); + this.toggleRes(thisSelect, row); + this.router.navigate([`/resources/${row.resId}`]); + } else { + row.checked = !row.checked; + this.toggleRes(row.checked ? thisSelect : thisDeselect, row); + } + } + + launchSearch(criteria: any) { + this.criteria = JSON.parse(JSON.stringify(criteria)); + if (!this.initSearch) { + this.initResultList(); + this.initSearch = true; + } else { + this.refreshDao(); + } + this.appCriteriaTool.toggleTool(false); + } + + initResultList() { + this.resultListDatabase = new ResultListHttpDao(this.http, this.criteriaSearchService); + // If the user changes the sort order, reset back to the first page. + this.paginator.pageIndex = this.listProperties.page; + this.paginator.pageSize = this.listProperties.pageSize; + this.sort.sortChange.subscribe(() => this.paginator.pageIndex = 0); + + // When list is refresh (sort, page, filters) + merge(this.sort.sortChange, this.paginator.page, this.filtersChange) + .pipe( + takeUntil(this.destroy$), + startWith({}), + switchMap(() => { + this.isLoadingResults = true; + return this.resultListDatabase!.getRepoIssues( + this.sort.active, this.sort.direction, this.paginator.pageIndex, this.searchUrl, this.listProperties, this.paginator.pageSize, this.criteria); + }), + map((data: any) => { + // Flip flag to show that loading has finished. + this.isLoadingResults = false; + data = this.processPostData(data); + this.resultsLength = data.count; + this.allResInBasket = data.allResources; + // this.headerService.setHeader('Dossier : ' + this.folderInfo.label); + return data.resources; + }), + catchError((err: any) => { + this.notify.handleErrors(err); + // this.router.navigate(['/home']); + this.isLoadingResults = false; + return of(false); + }) + ).subscribe(data => this.data = data); + } + + goTo(row: any) { + // this.criteriaSearchService.filterMode = false; + if (this.docUrl === '../rest/resources/' + row.resId + '/content' && this.sidenavRight.opened) { + this.sidenavRight.close(); + } else { + this.docUrl = '../rest/resources/' + row.resId + '/content'; + this.currentChrono = row.chrono; + this.innerHtml = this.sanitizer.bypassSecurityTrustHtml( + '<iframe style=\'height:100%;width:100%;\' src=\'' + this.docUrl + '\' class=\'embed-responsive-item\'>' + + '</iframe>'); + this.sidenavRight.open(); + } + } + + goToDetail(row: any) { + this.router.navigate([`/resources/${row.resId}`]); + } + + goToFolder(folder: any) { + this.router.navigate([`/folders/${folder.id}`]); + } + + togglePanel(mode: string, row: any) { + const thisSelect = { checked: true }; + const thisDeselect = { checked: false }; + row.checked = true; + this.toggleAllRes(thisDeselect); + this.toggleRes(thisSelect, row); + + if (this.currentResource.resId === row.resId && this.sidenavRight.opened && this.currentMode === mode) { + this.sidenavRight.close(); + } else { + this.currentMode = mode; + this.currentResource = row; + this.appPanelList.loadComponent(mode, row); + this.sidenavRight.open(); + } + } + + refreshBadgeNotes(nb: number) { + this.currentResource.countNotes = nb; + } + + refreshFolderInformations() { + this.http.get('../rest/folders/' + this.folderInfo.id) + .subscribe((data: any) => { + const keywordEntities = [{ + keyword: 'ALL_ENTITIES', + text: this.translate.instant('lang.allEntities'), + }]; + this.folderInfo = { + 'id': data.folder.id, + 'label': data.folder.label, + 'ownerDisplayName': data.folder.ownerDisplayName, + 'entitiesSharing': data.folder.sharing.entities.map((entity: any) => { + if (!this.functions.empty(entity.label)) { + return entity.label; + } else { + return keywordEntities.filter((element: any) => element.keyword === entity.keyword)[0].text; + } + }), + }; + this.headerService.setHeader(this.folderInfo.label, '', 'fa fa-folder-open'); + }); + } + + refreshBadgeAttachments(nb: number) { + this.currentResource.countAttachments = nb; + } + + refreshDao() { + this.paginator.pageIndex = this.listProperties.page; + this.filtersChange.emit(); + } + + refreshDaoAfterAction() { + this.sidenavRight.close(); + this.refreshDao(); + const e: any = { checked: false }; + this.toggleAllRes(e); + } + + viewThumbnail(row: any) { + if (row.hasDocument) { + this.thumbnailUrl = '../rest/resources/' + row.resId + '/thumbnail'; + $('#viewThumbnail').show(); + $('#listContent').css({ 'overflow': 'hidden' }); + } + } + + closeThumbnail() { + $('#viewThumbnail').hide(); + $('#listContent').css({ 'overflow': 'auto' }); + } + + processPostData(data: any) { + data.resources.forEach((element: any) => { + // Process main datas + Object.keys(element).forEach((key) => { + if (key === 'statusImage' && element[key] == null) { + element[key] = 'fa-question undefined'; + } else if ((element[key] == null || element[key] === '') && ['closingDate', 'countAttachments', 'countNotes', 'display', 'mailTracking', 'hasDocument'].indexOf(key) === -1) { + element[key] = this.translate.instant('lang.undefined'); + } + }); + + element['checked'] = this.selectedRes.indexOf(element['resId']) !== -1; + }); + + return data; + } + + toggleRes(e: any, row: any) { + if (this.singleSelection) { + this.toggleAllRes({ checked: false }); + } + if (e.checked) { + if (this.selectedRes.indexOf(row.resId) === -1) { + this.selectedRes.push(row.resId); + row.checked = true; + } + } else { + const index = this.selectedRes.indexOf(row.resId); + this.selectedRes.splice(index, 1); + row.checked = false; + } + } + + toggleAllRes(e: any) { + this.selectedRes = []; + if (e.checked) { + this.data.forEach((element: any) => { + element['checked'] = true; + }); + this.selectedRes = JSON.parse(JSON.stringify(this.allResInBasket)); + } else { + this.data.forEach((element: any) => { + element['checked'] = false; + }); + } + } + + selectSpecificRes(row: any) { + const thisSelect = { checked: true }; + const thisDeselect = { checked: false }; + + this.toggleAllRes(thisDeselect); + this.toggleRes(thisSelect, row); + } + + open({ x, y }: MouseEvent, row: any) { + const thisSelect = { checked: true }; + const thisDeselect = { checked: false }; + if (row.checked === false) { + row.checked = true; + this.toggleAllRes(thisDeselect); + this.toggleRes(thisSelect, row); + } + if (this.actionMode) { + this.actionsList.open(x, y, row); + } + + // prevents default + return false; + } + + listTodrag() { + return this.foldersService.getDragIds(); + } + + toggleMailTracking(row: any) { + if (!row.mailTracking) { + this.http.post('../rest/resources/follow', { resources: [row.resId] }).pipe( + tap(() => { + this.headerService.nbResourcesFollowed++; + row.mailTracking = !row.mailTracking; + }), + catchError((err: any) => { + this.notify.handleSoftErrors(err); + return of(false); + }) + ).subscribe(); + } else { + this.http.request('DELETE', '../rest/resources/unfollow', { body: { resources: [row.resId] } }).pipe( + tap(() => { + this.headerService.nbResourcesFollowed--; + row.mailTracking = !row.mailTracking; + }), + catchError((err: any) => { + this.notify.handleSoftErrors(err); + return of(false); + }) + ).subscribe(); + } + } + + viewDocument(row: any) { + this.http.get(`../rest/resources/${row.resId}/content?mode=view`, { responseType: 'blob' }).pipe( + tap((data: any) => { + const file = new Blob([data], { type: 'application/pdf' }); + const fileURL = URL.createObjectURL(file); + const newWindow = window.open(); + newWindow.document.write(`<iframe style="width: 100%;height: 100%;margin: 0;padding: 0;" src="${fileURL}" frameborder="0" allowfullscreen></iframe>`); + newWindow.document.title = row.chrono; + }), + catchError((err: any) => { + this.notify.handleSoftErrors(err); + return of(false); + }) + ).subscribe(); + } + + emptyCriteria() { + return Object.keys(this.criteria).length === 0; + } + + isArrayType(value: any) { + return (Array.isArray(value)); + } + + removeCriteria(identifier: string, value: any = null) { + if (identifier !== '_ALL') { + const tmpArrCrit = []; + if (value === null || this.criteria[identifier].values.length === 1) { + this.criteria[identifier].values = []; + } else { + const indexArr = this.criteria[identifier].values.indexOf(value); + this.criteria[identifier].values.splice(indexArr, 1); + } + } else { + Object.keys(this.criteria).forEach(key => { + this.criteria[key].values = []; + }); + } + this.appCriteriaTool.refreshCriteria(this.criteria); + } + + updateFilters() { + this.listProperties.page = 0; + + this.criteriaSearchService.updateListsProperties(this.listProperties); + + this.refreshDao(); + } + + changeOrderDir() { + if (this.listProperties.orderDir === 'ASC') { + this.listProperties.orderDir = 'DESC'; + } else { + this.listProperties.orderDir = 'ASC'; + } + this.updateFilters(); + } + + getSelectedResources() { + return this.selectedRes; + } +} +export interface BasketList { + folder: any; + resources: any[]; + countResources: number; + allResources: number[]; +} + +export class ResultListHttpDao { + + constructor(private http: HttpClient, private criteriaSearchService: CriteriaSearchService) { } + + getRepoIssues(sort: string, order: string, page: number, href: string, filters: any, pageSize: number, criteria: any): Observable<BasketList> { + this.criteriaSearchService.updateListsPropertiesPage(page); + this.criteriaSearchService.updateListsPropertiesPageSize(pageSize); + this.criteriaSearchService.updateListsPropertiesCriteria(criteria); + const offset = page * pageSize; + const requestUrl = `${href}?limit=${pageSize}&offset=${offset}&order=${filters.order}&orderDir=${filters.orderDir}`; + return this.http.post<BasketList>(requestUrl, this.criteriaSearchService.formatDatas(JSON.parse(JSON.stringify(criteria)))); + } +} diff --git a/src/frontend/app/app.module.ts b/src/frontend/app/app.module.ts index 2e8450f94799639d25070eb3dc6933785330f785..7caa7b6a3ad0fbddecd864b8542b5ba3ecf1f0ee 100755 --- a/src/frontend/app/app.module.ts +++ b/src/frontend/app/app.module.ts @@ -26,8 +26,6 @@ import { ActionsService } from './actions/actions.service'; import { AppComponent } from './app.component'; // ACTIONS -import { SearchAdvListComponent } from './adv-search/list/search-adv-list.component'; - import { ConfirmActionComponent } from './actions/confirm-action/confirm-action.component'; import { DisabledBasketPersistenceActionComponent } from './actions/disabled-basket-persistence-action/disabled-basket-persistence-action.component'; import { EnabledBasketPersistenceActionComponent } from './actions/enabled-basket-persistence-action/enabled-basket-persistence-action.component'; @@ -84,6 +82,7 @@ import { SelectIndexingModelComponent } from './indexation/select-indexing-model import { FilterToolComponent } from './adv-search/filter-tool/filter-tool.component'; import { AdvSearchComponent } from './adv-search/adv-search.component'; +import { SearchResultListComponent } from './adv-search/result-list/search-result-list.component'; import { AboutUsComponent } from './about-us.component'; import { ActivateUserComponent } from './activate-user.component'; import { AddAvisModelModalComponent } from './avis/addAvisModel/add-avis-model-modal.component'; @@ -159,6 +158,7 @@ export class MyHammerConfig extends HammerGestureConfig { FilterToolComponent, PanelListComponent, AdvSearchComponent, + SearchResultListComponent, AboutUsComponent, ActivateUserComponent, AddAvisModelModalComponent, @@ -194,7 +194,6 @@ export class MyHammerConfig extends HammerGestureConfig { DevLangComponent, DevToolComponent, AcknowledgementReceptionComponent, - SearchAdvListComponent, ConfirmActionComponent, ResMarkAsReadActionComponent, EnabledBasketPersistenceActionComponent, diff --git a/src/frontend/app/linkedResource/linkResourceModal/link-resource-modal.component.html b/src/frontend/app/linkedResource/linkResourceModal/link-resource-modal.component.html index 3c557ecf36b0dc5934e4520c831422f22d0105d1..e95b63f58b5d237fe6d91234b48ff6d35638a9b4 100755 --- a/src/frontend/app/linkedResource/linkResourceModal/link-resource-modal.component.html +++ b/src/frontend/app/linkedResource/linkResourceModal/link-resource-modal.component.html @@ -7,13 +7,16 @@ <mat-icon class="fa fa-times"></mat-icon> </button></h1> <mat-dialog-content> - <app-criteria-tool (searchUrlGenerated)="launchSearch($event)" - [defaultCriteria]="['resourceField','contactField']"></app-criteria-tool> - <search-adv-list #appSearchAdvList [excludeRes]="[data.resId]"></search-adv-list> + <div class="bg-primary"> + <app-criteria-tool #appCriteriaTool [openedPanel]="true" (searchUrlGenerated)="appSearchResultList.launchSearch($event)" + [defaultCriteria]="['chrono', 'subject','recipients','senders']"></app-criteria-tool> + <div id="toolTemplate" style="padding-top: 10px;"></div> + </div> + <app-search-result-list #appSearchResultList [actionMode]="false" [hideFilter]="true" [appCriteriaTool]="appCriteriaTool" [standalone]="true"></app-search-result-list> </mat-dialog-content> <span class="divider-modal"></span> <div mat-dialog-actions class="actions"> - <button mat-raised-button color="primary" [disabled]="appSearchAdvList.getSelectedRessources().length === 0" - (click)="linkResources()">{{'lang.linkSelectedResources' | translate}}</button> + <button mat-raised-button color="primary" + (click)="linkResources()" [disabled]="!isSelectedResources()">{{'lang.linkSelectedResources' | translate}}</button> </div> </div> \ No newline at end of file diff --git a/src/frontend/app/linkedResource/linkResourceModal/link-resource-modal.component.scss b/src/frontend/app/linkedResource/linkResourceModal/link-resource-modal.component.scss index a51c80f154a1252f042f167d98280c25b770ba7d..9175d08ff8646a4a33fa1a7edb3a5d56d972caa5 100644 --- a/src/frontend/app/linkedResource/linkResourceModal/link-resource-modal.component.scss +++ b/src/frontend/app/linkedResource/linkResourceModal/link-resource-modal.component.scss @@ -1 +1,10 @@ @import '../../../css/vars.scss'; + +.mat-dialog-content { + padding: 0px; +} + +.bg-primary { + padding: 10px; + background: $primary; +} \ No newline at end of file diff --git a/src/frontend/app/linkedResource/linkResourceModal/link-resource-modal.component.ts b/src/frontend/app/linkedResource/linkResourceModal/link-resource-modal.component.ts index c10cb0aef1cd42c1f0309d15ea40f70613d3d1dd..fbc26cf6c814e98ee72693853730d414e94aa6de 100644 --- a/src/frontend/app/linkedResource/linkResourceModal/link-resource-modal.component.ts +++ b/src/frontend/app/linkedResource/linkResourceModal/link-resource-modal.component.ts @@ -1,22 +1,21 @@ -import { Component, Inject, ViewChild } from '@angular/core'; +import { Component, Inject, OnInit, ViewChild } from '@angular/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { TranslateService } from '@ngx-translate/core'; import { HttpClient } from '@angular/common/http'; -import { SearchAdvListComponent } from '../../adv-search/list/search-adv-list.component'; import { NotificationService } from '@service/notification/notification.service'; import { of } from 'rxjs'; import { catchError, tap } from 'rxjs/operators'; +import { SearchResultListComponent } from '@appRoot/adv-search/result-list/search-result-list.component'; @Component({ templateUrl: 'link-resource-modal.component.html', styleUrls: ['link-resource-modal.component.scss'], }) -export class LinkResourceModalComponent { - +export class LinkResourceModalComponent implements OnInit { searchUrl: string = ''; - @ViewChild('appSearchAdvList', { static: false }) appSearchAdvList: SearchAdvListComponent; + @ViewChild('appSearchResultList', { static: false }) appSearchResultList: SearchResultListComponent; constructor( public translate: TranslateService, @@ -28,22 +27,21 @@ export class LinkResourceModalComponent { ngOnInit(): void { } - launchSearch(value: any) { - this.searchUrl = value; - this.appSearchAdvList.refreshDao(value); - } - linkResources() { - const selectedRes = this.appSearchAdvList.getSelectedRessources().filter(res => res !== this.data.resId); + const selectedRes = this.appSearchResultList.getSelectedResources().filter(res => res !== this.data.resId); this.http.post(`../rest/resources/${this.data.resId}/linkedResources`, { linkedResources: selectedRes }).pipe( tap(() => { this.dialogRef.close('success'); }), catchError((err: any) => { - this.notify.handleSoftErrors(err) + this.notify.handleSoftErrors(err); return of(false); }) ).subscribe(); } + + isSelectedResources() { + return this.appSearchResultList !== undefined && this.appSearchResultList.getSelectedResources().filter(res => res !== this.data.resId).length > 0; + } } diff --git a/src/frontend/plugins/select-search/select-search.component.html b/src/frontend/plugins/select-search/select-search.component.html index 64564bed5e86f97e1fa89a3e31292d4aca62fdef..3943ad0959b2875ee4453d517a87f3db3aaa7b15 100644 --- a/src/frontend/plugins/select-search/select-search.component.html +++ b/src/frontend/plugins/select-search/select-search.component.html @@ -1,6 +1,6 @@ <mat-form-field [class]="class + ' search-select'" [floatLabel]="appService.getViewMode() || showLabel ? '' : 'never'"> <mat-label *ngIf="appService.getViewMode() || showLabel">{{label}}</mat-label> - <mat-select [id]="id" [compareWith]="compareWithFn" [formControl]="formControlSelect" [placeholder]="placeholderLabel" #test + <mat-select [id]="id" [formControl]="formControlSelect" [placeholder]="placeholderLabel" #test (selectionChange)="launchEvent($event)" [required]="required" [multiple]="multiple"> <mat-select-trigger *ngIf="multiple"> {{formControlSelect.value.length > 0 ? getFirstDataLabel() : ''}} diff --git a/src/frontend/plugins/select-search/select-search.component.ts b/src/frontend/plugins/select-search/select-search.component.ts index c1092bc200a57f94eb7a8727f049b31a7bfe4788..94d692ed6c0e880355491474ef4d2b859e0a0b45 100755 --- a/src/frontend/plugins/select-search/select-search.component.ts +++ b/src/frontend/plugins/select-search/select-search.component.ts @@ -114,6 +114,9 @@ export class PluginSelectSearchComponent implements OnInit, OnDestroy, AfterView private sortPipe: SortPipe) { } ngOnInit() { + if (this.multiple) { + this.matSelect.compareWith = (item1: any, item2: any) => item1 && item2 ? item1.id === item2.id : item1 === item2; + } if (this.optGroupList !== null) { this.initOptGroups(); } @@ -411,10 +414,6 @@ export class PluginSelectSearchComponent implements OnInit, OnDestroy, AfterView // return this.returnValue === 'id' ? this.formControlSelect.value[0].label.replace(/\u00a0/g, '') : this.formControlSelect.value.map((item: any) => item !== null ? item.label : this.translate.instant('lang.emptyValue'))[0]; } - compareWithFn(item1: any, item2: any) { - return item1 && item2 ? item1.id === item2.id : item1 === item2; - } - emptyData() { return this.returnValue === 'id' ? null : {id: null, label : this.translate.instant('lang.emptyValue')}; } diff --git a/src/frontend/service/header.service.ts b/src/frontend/service/header.service.ts index 92d6edf8b6e3b0f4e33e0cfccdee693214654327..8cfef391839009bc2449793e075e7a6323aeef96 100755 --- a/src/frontend/service/header.service.ts +++ b/src/frontend/service/header.service.ts @@ -151,4 +151,24 @@ export class HeaderService { // Attach portal to host this.portalHost.attach(templatePortal); } + + initTemplate(template: TemplateRef<any>, viewContainerRef: ViewContainerRef, id: string = 'adminMenu', mode: string = '') { + + // Create a portalHost from a DOM element + this.portalHost = new DomPortalHost( + document.querySelector(`#${id}`), + this.componentFactoryResolver, + this.appRef, + this.injector + ); + + // Create a template portal + const templatePortal = new TemplatePortal( + template, + viewContainerRef + ); + + // Attach portal to host + this.portalHost.attach(templatePortal); + } }