diff --git a/src/frontend/app/adv-search/adv-search.component.html b/src/frontend/app/adv-search/adv-search.component.html new file mode 100644 index 0000000000000000000000000000000000000000..83676268579a4da60eaff33736c3c8119fb6f12c --- /dev/null +++ b/src/frontend/app/adv-search/adv-search.component.html @@ -0,0 +1,188 @@ +<mat-sidenav-container class="maarch-container"> + <ng-template #adminMenuTemplate> + <app-filter-tool-adv-search></app-filter-tool-adv-search> + </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"> + <header-left></header-left> + </div> + <div class="bg-head-title-tool"> + <header-right></header-right> + </div> + </div> + <div class="bg-head-content" [class.fullContainer]="appService.getViewMode()"> + <app-criteria-tool (searchUrlGenerated)="launchSearch($event)" + [defaultCriteria]="['resourceField','contactField']" style="width:100%;"></app-criteria-tool> + <!--<app-filters-tool style="flex:1;overflow-x: auto;overflow-y: hidden;" #filtersTool + [listProperties]="this.listProperties" [totalRes]="allResInBasket.length" + [selectedRes]="selectedRes" [routeDatas]="'/rest/folders/' + folderInfo.id + '/filters'" + (toggleAllRes)="toggleAllRes($event)" (refreshEventAfterAction)="refreshDaoAfterAction()" + (refreshEvent)="refreshDao()" [title]="this.translate.instant('lang.searchMailInFolder')"> + </app-filters-tool>--> + </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> + <!--<app-folder-action-list #actionsList [contextMode]="false" [currentFolderInfo]="folderInfo" + [totalRes]="allResInBasket.length" [selectedRes]="selectedRes" + (refreshEvent)="refreshDaoAfterAction()" + (refreshPanelFolders)="foldersService.getFolders()"> + </app-folder-action-list>--> + </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 *ngIf="!appService.getViewMode()" style="cursor:pointer;" + class="main-info-status"> + <mat-icon *ngIf="row.isLocked !== true" 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> + <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()" 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" 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"> + <div *ngIf="!row.allowed" color="warn"> + {{'lang.documentOutOfPerimeter' | translate}} + </div> + <ng-container *ngIf="row.allowed"> + <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 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 title="{{'lang.linkDetails' | translate}}" + (click)="$event.stopPropagation();goToDetail(row);"> + <mat-icon fontSet="fas" fontIcon="fa-info-circle fa-2x"></mat-icon> + </button> + </ng-container> + </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);" (click)="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> + </div> + </div> + </mat-sidenav-content> + <mat-sidenav #snav2 [fixedInViewport]="appService.getViewMode()" position='end' + [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> + </mat-sidenav> +</mat-sidenav-container> +<app-folder-action-list #actionsListContext [contextMode]="true" [currentFolderInfo]="folderInfo" + [totalRes]="allResInBasket.length" [selectedRes]="selectedRes" (refreshEvent)="refreshDaoAfterAction()" + (refreshPanelFolders)="foldersService.getFolders()"> +</app-folder-action-list> diff --git a/src/frontend/app/adv-search/adv-search.component.scss b/src/frontend/app/adv-search/adv-search.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..f39600d1d0db3556cf15c19d5023a81108487d70 --- /dev/null +++ b/src/frontend/app/adv-search/adv-search.component.scss @@ -0,0 +1,113 @@ +@import '../../css/vars.scss'; + +.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; +} + +.banner-opened { + display: flex; + flex-direction: column; + margin: 10px; + border-radius: 20px; + border: solid 1px #ccc; + position: relative; + padding: 20px; + background: lighten($primary, 10%); + width: 100%; + + .title { + white-space: pre; + overflow: hidden; + max-width: 85%; + text-overflow: ellipsis; + z-index: 1; + font-size: 20px; + font-weight: bold; + letter-spacing: 2px; + position: absolute; + top: -18px; + left: 20px; + padding: 0px; + margin: 0px; + color: white; + + &-divider { + position: absolute; + width: 99%; + z-index: -1; + top: 17px; + background: lighten($primary, 10%); + height: 1px; + } + } + + .content { + display: flex; + font-size: 16px; + + &-item { + flex: 1; + padding-left: 20px; + padding-right: 20px; + } + + .private { + font-style: italic; + display: flex; + justify-content: flex-end; + align-items: center; + } + } +} + +.banner-closed { + display: flex; + flex-direction: column; + margin: 10px; + position: relative; + width: 100%; + + .title { + white-space: pre; + overflow: hidden; + max-width: 85%; + text-overflow: ellipsis; + z-index: 1; + font-size: 20px; + font-weight: bold; + letter-spacing: 2px; + position: absolute; + top: -18px; + left: 20px; + padding: 0px; + margin: 0px; + color: white; + } +} + +.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; +} diff --git a/src/frontend/app/adv-search/adv-search.component.ts b/src/frontend/app/adv-search/adv-search.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..a7975259d461660eaae13de969cca3117ad09993 --- /dev/null +++ b/src/frontend/app/adv-search/adv-search.component.ts @@ -0,0 +1,447 @@ +import { Component, OnInit, ViewChild, EventEmitter, ViewContainerRef, OnDestroy, TemplateRef } 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 '../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 { FiltersListService } from '../../service/filtersList.service'; +import { FoldersService } from '../folder/folders.service'; +import { FunctionsService } from '../../service/functions.service'; +import { Subject } from 'rxjs/internal/Subject'; +import { Subscription } from 'rxjs/internal/Subscription'; +import { merge } from 'rxjs/internal/observable/merge'; +import { of } from 'rxjs/internal/observable/of'; +import { Observable } from 'rxjs/internal/Observable'; + +declare var $: any; + +@Component({ + templateUrl: 'adv-search.component.html', + styleUrls: ['adv-search.component.scss'] +}) +export class AdvSearchComponent implements OnInit, OnDestroy { + + loading: boolean = false; + docUrl: string = ''; + public innerHtml: SafeHtml; + basketUrl: string; + 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; + + @ViewChild('adminMenuTemplate', { static: true }) adminMenuTemplate: TemplateRef<any>; + @ViewChild('actionsListContext', { static: true }) actionsList: FolderActionListComponent; + @ViewChild('appPanelList', { static: true }) appPanelList: PanelListComponent; + + currentSelectedChrono: string = ''; + + @ViewChild(MatPaginator, { static: true }) paginator: MatPaginator; + @ViewChild('tableBasketListSort', { static: true }) sort: MatSort; + @ViewChild('basketHome', { static: true }) basketHome: BasketHomeComponent; + + constructor( + public translate: TranslateService, + private router: Router, + private route: ActivatedRoute, + public http: HttpClient, + public dialog: MatDialog, + private sanitizer: DomSanitizer, + private headerService: HeaderService, + public filtersListService: FiltersListService, + private notify: NotificationService, + public overlay: Overlay, + public viewContainerRef: ViewContainerRef, + public appService: AppService, + public foldersService: FoldersService, + public functions: FunctionsService) { } + + ngOnInit(): void { + this.headerService.sideBarAdmin = true; + + this.loading = false; + + this.isLoadingResults = false; + + this.headerService.injectInSideBarLeft(this.adminMenuTemplate, this.viewContainerRef, 'adminMenu'); + this.headerService.setHeader(this.translate.instant('lang.searchMails'), '', 'fa fa-search'); + + /*this.route.params.subscribe(params => { + this.folderInfoOpened = false; + this.dragInit = true; + this.destroy$.next(true); + + this.http.get('../rest/folders/' + params['folderId']) + .subscribe((data: any) => { + const keywordEntities = [{ + keyword: 'ALL_ENTITIES', + text: this.translate.instant('lang.allEntities'), + }]; + this.folderInfo = { + 'id': params['folderId'], + '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.foldersService.setFolder(this.folderInfo); + this.headerService.setHeader(this.folderInfo.label, '', 'fa fa-folder-open'); + + }); + this.basketUrl = '../rest/folders/' + params['folderId'] + '/resources'; + this.filtersListService.filterMode = false; + this.selectedRes = []; + this.sidenavRight.close(); + + this.listProperties = this.filtersListService.initListsProperties(this.headerService.user.id, 0, params['folderId'], 'folder'); + + setTimeout(() => { + this.dragInit = false; + }, 1000); + this.initResultList(); + + }, + (err: any) => { + this.notify.handleErrors(err); + });*/ + } + + ngOnDestroy() { + this.destroy$.next(true); + this.subscription.unsubscribe(); + } + + launchSearch(value: any) { + // this.searchUrl = value; + this.refreshDao(); + } + + initResultList() { + this.resultListDatabase = new ResultListHttpDao(this.http, this.filtersListService); + // 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.basketUrl, this.filtersListService.getUrlFilters(), this.paginator.pageSize); + }), + map((data: any) => { + // Flip flag to show that loading has finished. + this.isLoadingResults = false; + data = this.processPostData(data); + this.resultsLength = data.countResources; + 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.filtersListService.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(); + } +} +export interface BasketList { + folder: any; + resources: any[]; + countResources: number; + allResources: number[]; +} + +export class ResultListHttpDao { + + constructor(private http: HttpClient, private filtersListService: FiltersListService) { } + + getRepoIssues(sort: string, order: string, page: number, href: string, filters: string, pageSize: number): Observable<BasketList> { + this.filtersListService.updateListsPropertiesPage(page); + this.filtersListService.updateListsPropertiesPageSize(pageSize); + const offset = page * pageSize; + const requestUrl = `${href}?limit=${pageSize}&offset=${offset}${filters}`; + + return this.http.get<BasketList>(requestUrl); + } +} diff --git a/src/frontend/app/adv-search/filter-tool/filter-tool.component.html b/src/frontend/app/adv-search/filter-tool/filter-tool.component.html new file mode 100644 index 0000000000000000000000000000000000000000..25c95be3c09fa1a48dba80bb364c8f4aead19dff --- /dev/null +++ b/src/frontend/app/adv-search/filter-tool/filter-tool.component.html @@ -0,0 +1,14 @@ +<mat-expansion-panel #basketPanel class="FilterContainer" expanded> + <mat-expansion-panel-header> + <mat-panel-title> + <i class="fas fa-filter panelIconMenu"></i> {{'lang.filterBy' | translate}} + </mat-panel-title> + </mat-expansion-panel-header> + <mat-selection-list *ngFor="let filterKey of filters | keyvalue" class="filter-list"> + <div class="catGroupTitle">{{filterKey.key}} ({{filters[filterKey.key].length}})</div> + <mat-divider style="width: 80%;"></mat-divider> + <mat-list-option color="primary" class="catContent" *ngFor="let cat of filters[filterKey.key]" [selected]="cat.selected" checkboxPosition="before"> + <span class="catLabel" [title]="cat.label">{{cat.label}}</span><span class="catBadge">{{cat.resCount}}</span> + </mat-list-option> + </mat-selection-list> +</mat-expansion-panel> diff --git a/src/frontend/app/adv-search/filter-tool/filter-tool.component.scss b/src/frontend/app/adv-search/filter-tool/filter-tool.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..a1fd6e390869a8fed32b45ec99db71be1f5432d0 --- /dev/null +++ b/src/frontend/app/adv-search/filter-tool/filter-tool.component.scss @@ -0,0 +1,56 @@ +@import '../../../css/vars.scss'; + +.catGroupTitle { + color: $primary; + font-size: 14px; + font-weight: 500; + padding-left: 10px; +} + +.catContent { + font-size: 12px; + font-weight: bold; +} + +.catLabel { + flex: 1; +} + +.catBadge { + color: #135f7f; + font-size: 14px; + font-weight: bold !important; + min-width: auto; +} + +.filter-list { + ::ng-deep.mat-list-text { + flex-direction: row; + } +} + +.FilterContainer { + border-radius: 0px; + box-shadow:none; + + ::ng-deep.mat-expansion-panel-body { + padding: 0; + } + + .mat-expansion-panel-header-title { + color: #135F7F; + display: flex; + align-items: center; + font-size: 14px; + font-weight: 500; + } + + .mat-line { + font-size: 12px; + font-weight: bold; + } +} + +.panelIconMenu { + font-size: 22px; +} \ No newline at end of file diff --git a/src/frontend/app/adv-search/filter-tool/filter-tool.component.ts b/src/frontend/app/adv-search/filter-tool/filter-tool.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..bc1416d6b3961dae4e4534abeda9ac2466d140dc --- /dev/null +++ b/src/frontend/app/adv-search/filter-tool/filter-tool.component.ts @@ -0,0 +1,108 @@ +import { Component, OnInit, ViewChild, ElementRef, EventEmitter, Output, Input } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { TranslateService } from '@ngx-translate/core'; +import { AppService } from '../../../service/app.service'; +import { FunctionsService } from '../../../service/functions.service'; +import { Observable } from 'rxjs'; +import { FormControl } from '@angular/forms'; +import { startWith, map } from 'rxjs/operators'; +import { LatinisePipe } from 'ngx-pipes'; +import { MatExpansionPanel } from '@angular/material/expansion'; + +@Component({ + selector: 'app-filter-tool-adv-search', + templateUrl: 'filter-tool.component.html', + styleUrls: ['filter-tool.component.scss'] +}) +export class FilterToolComponent implements OnInit { + + filters = { + categories: [], + priorities: [], + statuses: [], + doctypes: [], + destinations: [], + folders: [] + }; + constructor( + public translate: TranslateService, + public http: HttpClient, + public appService: AppService, + public functions: FunctionsService, + private latinisePipe: LatinisePipe) { } + + ngOnInit(): void { + + // FOR TEST + this.filters = { + categories: [ + { + id: 'outgoing', + label: 'Courrier départ', + resCount: 24, + selected: true, + }, + { + id: 'incoming', + label: 'Courrier arrivé', + resCount: 4, + selected: true, + } + ], + priorities: [ + { + id: 'IUDZIZOJD', + label : 'Normal', + resCount: 4, + selected: true, + }, + { + id: 'IUDZIZOJD', + label : 'Urgent', + resCount: 42, + selected: true, + } + ], + statuses: [ + { + id: 'IUDZIZOJD', + label : 'En cours', + resCount: 42, + selected: true, + } + ], + doctypes: [ + { + id: 'IUDZIZOJD', + label : 'Convocations', + resCount: 42, + selected: true, + }, + { + id: 'IUDZIZOJD', + label : 'Litiges', + resCount: 42, + selected: true, + } + ], + destinations: [ + { + id: 'IUDZIZOJD', + label : 'Pôle Jeunesse et Sport', + resCount: 42, + selected: true, + }, + { + id: 'IUDZIZOJD', + label : 'Pôle culturel', + resCount: 42, + selected: true, + } + ], + folders: [ + + ] + }; + } + +} diff --git a/src/frontend/app/app-routing.module.ts b/src/frontend/app/app-routing.module.ts index f73d48e6aab08f147805bce4d5d505b24f9ad53b..fc03a6480fff923b61476f3791f02275a4d83b8b 100755 --- a/src/frontend/app/app-routing.module.ts +++ b/src/frontend/app/app-routing.module.ts @@ -18,6 +18,7 @@ import { FollowedDocumentListComponent } from './home/followed-list/followed-doc import { FolderDocumentListComponent } from './folder/document-list/folder-document-list.component'; import { BasketListComponent } from './list/basket-list.component'; import { AcknowledgementReceptionComponent } from './registeredMails/acknowledgement-reception/acknowledgement-reception.component'; +import { AdvSearchComponent } from './adv-search/adv-search.component'; const routes: Routes = [ @@ -45,6 +46,7 @@ const routes: Routes = [ { path: 'basketList/users/:userSerialId/groups/:groupSerialId/baskets/:basketId', canActivate: [AppGuard], component: BasketListComponent }, { path: 'login', component: LoginComponent }, { path: 'registeredMail/acknowledgement', canActivate: [AppGuard], component: AcknowledgementReceptionComponent }, + { path: 'search', canActivate: [AppGuard], component: AdvSearchComponent }, { path: '', redirectTo: 'home', diff --git a/src/frontend/app/process/process.module.ts b/src/frontend/app/process/process.module.ts index 7867329c214cdd50b092944822fa7ecb070f5476..d1bcf7671c693921e041888d36123feee5df67b6 100644 --- a/src/frontend/app/process/process.module.ts +++ b/src/frontend/app/process/process.module.ts @@ -18,7 +18,10 @@ import { SentNumericPackagePageComponent } from '../sentResource/sent-numeric-pa import { ThesaurusModalComponent } from '../tag/indexing/thesaurus/thesaurus-modal.component'; import { SelectIndexingModelComponent } from '../indexation/select-indexing-model/select-indexing-model.component'; import { DocumentFormModule } from '../document-form.module'; -import {TranslateModule} from '@ngx-translate/core'; +import { AdvSearchComponent } from '../adv-search/adv-search.component'; +import { FilterToolComponent } from '../adv-search/filter-tool/filter-tool.component'; + +import { TranslateModule } from '@ngx-translate/core'; @NgModule({ imports: [ @@ -41,6 +44,8 @@ import {TranslateModule} from '@ngx-translate/core'; SentNumericPackagePageComponent, ThesaurusModalComponent, SelectIndexingModelComponent, + AdvSearchComponent, + FilterToolComponent ], exports: [ SharedModule, diff --git a/src/frontend/service/privileges.service.ts b/src/frontend/service/privileges.service.ts index 610609e3d8b2ebcd309d263e965acdb47ae8645a..781168bcabbbb1b7dc1a76ae29d4199353f68641 100755 --- a/src/frontend/service/privileges.service.ts +++ b/src/frontend/service/privileges.service.ts @@ -495,10 +495,10 @@ export class PrivilegeService { 'id': 'adv_search_mlb', 'label': 'lang.search', 'comment': 'lang.search', - 'route': 'index.php?page=search_adv&dir=indexing_searching', + 'route': '/search', 'style': 'fa fa-search', 'unit': 'application', - 'angular': false, + 'angular': true, 'shortcut': true }, {