diff --git a/lang/fr.json b/lang/fr.json index 689636464f2bfb0c9806214ff6b6c3fead0d3592..690e0e649c2846e2ca91cfc96f2b48bf12242ad9 100755 --- a/lang/fr.json +++ b/lang/fr.json @@ -307,7 +307,7 @@ "wrongCurrentPassword": "Le mot de passe actuel n'est pas correct", "alreadyUsedPassword": "Le mot de passe a déjà été utilisé", "homePage": "Page d'accueil", - "newDocumentToVisa": "Nouveau(x) document(s) à viser / signer", + "newDocumentToVisa": "Importer de nouveau(x) document(s)", "searchUser": "Rechercher un utilisateur" } } diff --git a/src/frontend/app/app.module.ts b/src/frontend/app/app.module.ts index 78f3133ea6c85c219de225b26557fed2a16c7395..76e8b22c63a415ded7ab31821b08e57fd3254370 100755 --- a/src/frontend/app/app.module.ts +++ b/src/frontend/app/app.module.ts @@ -24,6 +24,8 @@ import { LatinisePipe } from 'ngx-pipes'; import { AppMaterialModule } from './app-material.module'; import { AppRoutingModule } from './app-routing.module'; +import { DragDropDirective } from './plugins/upload-file-dnd.directive'; + // COMPONENTS import { AppComponent } from './app.component'; @@ -51,6 +53,7 @@ import { DocumentListComponent } from './document/document-list/document-list.co import { MainDocumentDetailComponent } from './document/main-document-detail/main-document-detail.component'; import { UpdatePasswordComponent } from './login/updatePassword/updatePassword.component'; import { PasswordModificationComponent } from './login/passwordModification/password-modification.component'; +import { VisaWorkflowModelsComponent } from './document/visa-workflow/models/visa-workflow-models.component'; // ADMINISTRATION import { AdminSidebarComponent } from './sidebar/administration/admin-sidebar.component'; @@ -85,6 +88,7 @@ import { SortPipe } from './plugins/sorting.pipe'; @NgModule({ declarations: [ AppComponent, + DragDropDirective, LoginComponent, HomeComponent, ForgotPasswordComponent, @@ -125,7 +129,8 @@ import { SortPipe } from './plugins/sorting.pipe'; UsersComponent, CheckConnectionComponent, CheckEmailConnectionComponent, - IndexationComponent + IndexationComponent, + VisaWorkflowModelsComponent ], imports: [ FormsModule, diff --git a/src/frontend/app/document/visa-workflow/models/visa-workflow-models.component.html b/src/frontend/app/document/visa-workflow/models/visa-workflow-models.component.html new file mode 100644 index 0000000000000000000000000000000000000000..477ac961586ae4c1c4ae5d040663e34cf0ecb918 --- /dev/null +++ b/src/frontend/app/document/visa-workflow/models/visa-workflow-models.component.html @@ -0,0 +1,17 @@ +<ion-list lines="none"> + <ion-list-header> + <ion-label color="secondary">Modèles de circuit</ion-label> + </ion-list-header> + <ion-item button *ngFor="let model of visaWorkflowModels" (click)="loadVisaWorkflow(model)"> + <ion-label>{{model.title}}</ion-label> + <ion-buttons slot="end"> + <ion-button fill="clear" slot="icon-only" shape="round" color="danger" (click)="$event.stopPropagation();removeModel(model)"> + <ion-icon slot="icon-only" name="trash-outline"></ion-icon> + </ion-button> + </ion-buttons> + </ion-item> + <ion-item button (click)="createModel()" [disabled]="currentWorkflow.length === 0"> + <ion-icon color="primary" slot="start" name="add-circle-outline"></ion-icon> + <ion-label color="primary">Nouveau modèle</ion-label> + </ion-item> +</ion-list> \ No newline at end of file diff --git a/src/frontend/app/document/visa-workflow/models/visa-workflow-models.component.scss b/src/frontend/app/document/visa-workflow/models/visa-workflow-models.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/frontend/app/document/visa-workflow/models/visa-workflow-models.component.ts b/src/frontend/app/document/visa-workflow/models/visa-workflow-models.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..088d8ef75c9081ea8a7e60eed989fb7250a0525c --- /dev/null +++ b/src/frontend/app/document/visa-workflow/models/visa-workflow-models.component.ts @@ -0,0 +1,159 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { AlertController, PopoverController } from '@ionic/angular'; +import { NotificationService } from '../../../service/notification.service'; +import { catchError, tap } from 'rxjs/operators'; +import { of } from 'rxjs'; + +@Component({ + selector: 'app-visa-workflow-models', + templateUrl: 'visa-workflow-models.component.html', + styleUrls: ['visa-workflow-models.component.scss'], +}) +export class VisaWorkflowModelsComponent implements OnInit { + + @Input() currentWorkflow: any[] = []; + + visaWorkflowModels: any[] = []; + + constructor( + public http: HttpClient, + public popoverController: PopoverController, + public alertController: AlertController, + public notificationService: NotificationService, + ) { } + + ngOnInit(): void { + this.getVisaUserModels(); + } + + async createModel() { + const alert = await this.alertController.create({ + header: 'Nouveau modèle', + message: 'Le circuit en cours sera sauvegardé.', + inputs: [ + { + name: 'title', + type: 'text', + placeholder: 'Label' + }, + ], + buttons: [ + { + text: 'Cancel', + role: 'cancel', + cssClass: 'secondary', + handler: (blah) => { + console.log('Confirm Cancel: blah'); + } + }, { + text: 'Ok', + handler: (data: any) => { + if (data.title !== '') { + this.saveModel(data.title); + return true; + } else { + this.notificationService.error('Label mandatory'); + return false; + } + } + } + ] + }); + + await alert.present(); + } + + saveModel(title: string) { + const objToSend: any = { + title: title, + items: this.currentWorkflow.map((item: any) => { + return { + userId: item.userId, + mode: item.mode, + signatureMode: 'standard' + }; + }) + }; + this.http.post('../rest/workflowTemplates', objToSend).pipe( + tap((res: any) => { + this.notificationService.success('Modèle créé'); + this.visaWorkflowModels.push({ id: res.id, title: title }); + }), + catchError(err => { + this.notificationService.handleErrors(err); + return of(false); + }) + ).subscribe(); + } + + async removeModel(model: any) { + const alert = await this.alertController.create({ + header: 'Supprimer', + message: 'Supprimer le modèle ?', + buttons: [ + { + text: 'Non', + role: 'cancel', + cssClass: 'secondary', + handler: () => { } + }, { + text: 'Oui', + handler: () => { + this.http.delete(`../rest/workflowTemplates/${model.id}`).pipe( + tap(() => { + this.visaWorkflowModels = this.visaWorkflowModels.filter((item: any) => item.id !== model.id); + this.notificationService.success(`Modèle ${model.title} supprimé`); + }), + catchError(err => { + this.notificationService.handleErrors(err); + return of(false); + }) + ).subscribe(); + } + } + ] + }); + + await alert.present(); + } + + getVisaUserModels() { + this.http.get('../rest/workflowTemplates').pipe( + tap((data: any) => { + this.visaWorkflowModels = data.workflowTemplates; + }), + catchError(err => { + this.notificationService.handleErrors(err); + return of(false); + }) + ).subscribe(); + } + + loadVisaWorkflow(model: any) { + this.http.get(`../rest/workflowTemplates/${model.id}`).pipe( + tap((data: any) => { + const workflows: any[] = data.workflowTemplate.items.map((item: any) => { + const obj: any = { + 'userId': item.userId, + 'userDisplay': item.userLabel, + 'mode': item.mode, + 'processDate': null, + 'current': false, + 'modes': [ + 'visa', + 'sign', + 'rgs' + ] + }; + return obj; + }); + this.popoverController.dismiss(workflows); + }), + catchError(err => { + this.notificationService.handleErrors(err); + return of(false); + }) + ).subscribe(); + } +} diff --git a/src/frontend/app/document/visa-workflow/visa-workflow.component.html b/src/frontend/app/document/visa-workflow/visa-workflow.component.html index 8f690a355f1e9c4cca928021fd53850a9be39a39..3d0bb2a1b3ef5b2c8e2f67e0a14f5c712df82e71 100644 --- a/src/frontend/app/document/visa-workflow/visa-workflow.component.html +++ b/src/frontend/app/document/visa-workflow/visa-workflow.component.html @@ -11,11 +11,20 @@ <ion-content (click)="resetVisaUsersList();"> <ng-container *ngIf="editMode"> <ion-item lines="none"> - <ion-searchbar #searchInput [(ngModel)]="visaUsersSearchVal" [placeholder]="'lang.searchUser' | translate" (ionChange)="getVisaUsers($event)"></ion-searchbar> + <ion-searchbar #searchInput [(ngModel)]="visaUsersSearchVal" [placeholder]="'lang.searchUser' | translate" + (ionChange)="getVisaUsers($event)" (ionFocus)="visaUsersSearchVal=''"></ion-searchbar> + <ion-buttons slot="end"> + <ion-button fill="clear" slot="icon-only" shape="round" color="primary" + (click)="openVisaWorkflowModels($event)" [title]="'Modèles de circuit'"> + <ion-icon slot="icon-only" name="albums-outline"></ion-icon> + </ion-button> + </ion-buttons> </ion-item> - <ion-list *ngIf="visaUsersList.length > 0" style="position: absolute;z-index: 1;width: 100%;box-shadow: 0 4px 2px -2px gray;"> + <ion-list *ngIf="visaUsersList.length > 0" + style="position: absolute;z-index: 1;width: 100%;box-shadow: 0 4px 2px -2px gray;"> <ion-item *ngFor="let user of visaUsersList"> - <ion-checkbox color="primary" slot="start" [value]="user" (ionChange)="addUser($event.detail.value, searchInput)"></ion-checkbox> + <ion-checkbox color="primary" slot="start" [value]="user" + (ionChange)="addUser($event.detail.value, searchInput)"></ion-checkbox> <ion-label>{{ user.firstname }} {{ user.lastname }}</ion-label> <ion-note slot="end">{{ user.email }}</ion-note> </ion-item> @@ -23,7 +32,7 @@ </ng-container> <ion-list *ngIf="visaWorkflow.length === 0"> <ion-item lines="none"> - <ion-label color="medium">Aucune personne dans le circuit</ion-label> + <ion-label class="ion-text-center" color="medium">Aucune personne dans le circuit</ion-label> </ion-item> </ion-list> <ion-list dnd-sortable-container [dropZones]="['boxers-zone']" [sortableData]="visaWorkflow"> @@ -32,11 +41,11 @@ <img [src]="diffusion.userPicture"> </ion-avatar> <ion-label>{{diffusion.userDisplay}}</ion-label> - <ion-select [value]="diffusion.mode" interface="popover"> - <ion-select-option [value]="mode" *ngFor="let mode of diffusion.modes">{{mode}}</ion-select-option> + <ion-select [(ngModel)]="diffusion.mode" interface="popover"> + <ion-select-option [value]="mode" *ngFor="let mode of diffusion.modes">{{'lang.' + mode + 'User' | translate}}</ion-select-option> </ion-select> <ion-button fill="clear" slot="end" shape="round" color="danger" (click)="removeUser(i)"> - <ion-icon slot="icon-only" name="trash-outline"></ion-icon> + <ion-icon slot="icon-only" name="trash-outline"></ion-icon> </ion-button> </ion-item> </ion-list> diff --git a/src/frontend/app/document/visa-workflow/visa-workflow.component.ts b/src/frontend/app/document/visa-workflow/visa-workflow.component.ts index 5696fd6859e556f770092b315f2e81d902469741..255c8ea6f9a91a5bce19634ca53e9a9fa7903142 100644 --- a/src/frontend/app/document/visa-workflow/visa-workflow.component.ts +++ b/src/frontend/app/document/visa-workflow/visa-workflow.component.ts @@ -5,6 +5,8 @@ import { AuthService } from '../../service/auth.service'; import { catchError, tap } from 'rxjs/operators'; import { of } from 'rxjs'; import { NotificationService } from '../../service/notification.service'; +import { PopoverController } from '@ionic/angular'; +import { VisaWorkflowModelsComponent } from './models/visa-workflow-models.component'; @Component({ selector: 'app-visa-workflow', @@ -27,6 +29,7 @@ export class VisaWorkflowComponent implements OnInit { public signaturesService: SignaturesContentService, public authService: AuthService, public notificationService: NotificationService, + public popoverController: PopoverController ) { } ngOnInit(): void { @@ -65,7 +68,7 @@ export class VisaWorkflowComponent implements OnInit { 'mode': 'visa', 'processDate': null, 'current': false, - 'modes' : [ + 'modes': [ 'visa', 'sign', 'rgs' @@ -82,18 +85,44 @@ export class VisaWorkflowComponent implements OnInit { } getAvatarUser(index: number) { - this.http.get('../rest/users/' + this.visaWorkflow[index].userId + '/picture').pipe( - tap((data: any) => { - this.visaWorkflow[index].userPicture = data.picture; - }), - catchError(err => { - this.notificationService.handleErrors(err); - return of(false); - }) - ).subscribe(); + if (this.visaWorkflow[index].userPicture === undefined && this.visaWorkflow[index].userDisplay !== '') { + this.http.get('../rest/users/' + this.visaWorkflow[index].userId + '/picture').pipe( + tap((data: any) => { + this.visaWorkflow[index].userPicture = data.picture; + }), + catchError(err => { + this.notificationService.handleErrors(err); + return of(false); + }) + ).subscribe(); + } } resetVisaUsersList() { this.visaUsersList = []; } + + async openVisaWorkflowModels(ev: any) { + const popover = await this.popoverController.create({ + component: VisaWorkflowModelsComponent, + componentProps: { currentWorkflow: this.visaWorkflow }, + event: ev, + }); + await popover.present(); + + popover.onDidDismiss() + .then((result: any) => { + if (result.role !== 'backdrop') { + console.log(result.data); + this.visaWorkflow = this.visaWorkflow.concat(result.data); + this.visaWorkflow.forEach((element: any, index: number) => { + this.getAvatarUser(index); + }); + } + }); + } + + getCurrentWorkflow() { + return this.visaWorkflow; + } } diff --git a/src/frontend/app/home/home.component.ts b/src/frontend/app/home/home.component.ts index 13ae61b449b44cd9873ba788d74cd298b76ceec4..1e366d8aa55641e80fe4116abe4cf9b9b3eb352a 100644 --- a/src/frontend/app/home/home.component.ts +++ b/src/frontend/app/home/home.component.ts @@ -19,6 +19,7 @@ export class HomeComponent implements OnInit { ngOnInit(): void { this.menu.enable(true, 'left-menu'); + this.menu.enable(false, 'right-menu'); this.menu.open('left-menu'); } diff --git a/src/frontend/app/plugins/upload-file-dnd.directive.ts b/src/frontend/app/plugins/upload-file-dnd.directive.ts new file mode 100644 index 0000000000000000000000000000000000000000..737a5590bd82ba5f4a0fcaa565d5bce012a611d2 --- /dev/null +++ b/src/frontend/app/plugins/upload-file-dnd.directive.ts @@ -0,0 +1,51 @@ +import { Directive, Output, EventEmitter, HostBinding, HostListener, Input } from '@angular/core'; + +@Directive({ + selector: '[appUploadFileDragDrop]' +}) +export class DragDropDirective { + + @Output() onFileDropped = new EventEmitter<any>(); + @Input() disabled: boolean = false; + + @HostBinding('style.background-color') private background = 'none'; + @HostBinding('style.opacity') private opacity = '1'; + + // Dragover listener + @HostListener('dragover', ['$event']) onDragOver(evt: any) { + if (!this.disabled) { + evt.preventDefault(); + evt.stopPropagation(); + this.background = '#9ecbec'; + this.opacity = '0.8'; + } + + } + + // Dragleave listener + @HostListener('dragleave', ['$event']) public onDragLeave(evt: any) { + if (!this.disabled) { + evt.preventDefault(); + evt.stopPropagation(); + this.background = 'rgba(255,255,255,0)'; + this.opacity = '1'; + } + + } + + // Drop listener + @HostListener('drop', ['$event']) public ondrop(evt: any) { + if (!this.disabled) { + evt.preventDefault(); + evt.stopPropagation(); + this.background = 'rgba(255,255,255,0)'; + this.opacity = '1'; + const files = evt.dataTransfer.files; + if (files.length > 0) { + this.onFileDropped.emit(files); + } + } + + } + +} \ No newline at end of file diff --git a/src/frontend/app/sidebar/sidebar.component.html b/src/frontend/app/sidebar/sidebar.component.html index a89ea54d2f2bfe1cbc82fd12b492fdd0ea84062f..d921121ec816e4741a7c01377928c881a75b686b 100755 --- a/src/frontend/app/sidebar/sidebar.component.html +++ b/src/frontend/app/sidebar/sidebar.component.html @@ -21,7 +21,7 @@ </ion-button> </ion-buttons> <ion-buttons slot="end"> - <ion-button (click)="openIndexation()" [title]="'Importer un document'"> + <ion-button (click)="openIndexation()" [title]="'lang.newDocumentToVisa' | translate"> <ion-icon slot="icon-only" name="cloud-upload-outline"></ion-icon> </ion-button> </ion-buttons>