diff --git a/lang/fr.json b/lang/fr.json index fd99421ef53b0d081398ad646b54d4d9907f0b41..5b629bfac1d9247f3ce36a9de0479453c9788109 100755 --- a/lang/fr.json +++ b/lang/fr.json @@ -313,6 +313,8 @@ "signatureModes": "Modes de signature", "documentsToUpload": "Document(s) à téléverser", "chooseDocuments": "Choisir des fichiers (pdf uniquement)", - "dndDocuments": "ou glisser-déposer des fichiers" + "dndDocuments": "ou glisser-déposer des fichiers", + "stamp": "Signature avec griffe", + "stampUser": "Signataire (griffe)" } } diff --git a/src/frontend/app/administration/user/user.component.html b/src/frontend/app/administration/user/user.component.html index 48665e5968b9b93e101fd781a428e47ecc9b2dfb..811d74994b89bc08dbb4d6b21fc4d181843e5714 100644 --- a/src/frontend/app/administration/user/user.component.html +++ b/src/frontend/app/administration/user/user.component.html @@ -39,7 +39,7 @@ <ion-label color="secondary">{{'lang.signatureModes' | translate}}</ion-label> </ion-list-header> <ion-item *ngFor="let signMode of signatureModes"> - <ion-label [style.color]="signMode.color">{{signMode.label}}</ion-label> + <ion-label [style.color]="signMode.color">{{'lang.' + signMode.id | translate}}</ion-label> <ion-checkbox slot="start" [checked]="user.signatureModes.indexOf(signMode.id) > -1" (ionChange)="toggleSignMode(signMode, $event.detail.checked)" [disabled]="signMode.id === 'stamp'"></ion-checkbox> </ion-item> </ion-list> diff --git a/src/frontend/app/administration/user/user.component.ts b/src/frontend/app/administration/user/user.component.ts index 86e4ab762abe1b29f19c1aa73a5ec76490fca62a..7663d91afe57d593711585e373bedfdea4c42d58 100644 --- a/src/frontend/app/administration/user/user.component.ts +++ b/src/frontend/app/administration/user/user.component.ts @@ -3,11 +3,12 @@ import { SignaturesContentService } from '../../service/signatures.service'; import { NotificationService } from '../../service/notification.service'; import { HttpClient, HttpHeaders } from '@angular/common/http'; import { MatDialog } from '@angular/material/dialog'; -import { map, finalize } from 'rxjs/operators'; +import { map, finalize, catchError, tap } from 'rxjs/operators'; import { ActivatedRoute, Router } from '@angular/router'; import { ConfirmComponent } from '../../plugins/confirm.component'; import { TranslateService } from '@ngx-translate/core'; import { AuthService } from '../../service/auth.service'; +import { of } from 'rxjs'; export interface User { @@ -110,10 +111,6 @@ export class UserComponent implements OnInit { .subscribe({ next: data => { this.user = data; - - // FOR TEST - this.user.signatureModes = ['stamp']; - this.userClone = JSON.parse(JSON.stringify(this.user)); this.title = this.user.firstname + ' ' + this.user.lastname; if (this.user.isRest) { @@ -126,29 +123,15 @@ export class UserComponent implements OnInit { } getSignatureModes() { - // FOR TEST - this.signatureModes = [ - { - id: 'rgs', - label: 'Signature RGS**', - color: '#cb4335' - }, - { - id: 'cagent', - label: 'Signature carte agent', - color: '#f39c12' - }, - { - id: 'eidas', - label: 'Signature eidas', - color: '#f39c12' - }, - { - id: 'stamp', - label: 'Signature griffe', - color: '#27ae60' - }, - ]; + this.http.get('../rest/signatureModes').pipe( + tap((data: any) => { + this.signatureModes = data; + }), + catchError((err: any) => { + this.notificationService.handleErrors(err); + return of(false); + }) + ).subscribe(); } canValidate() { @@ -334,7 +317,7 @@ export class UserComponent implements OnInit { if (state) { this.user.signatureModes.push(signMode.id); } else { - this.user.signatureModes = this.user.signatureModes.filter((item: any) => item.id !== signMode.id); + this.user.signatureModes = this.user.signatureModes.filter((item: any) => item !== signMode.id); } } } diff --git a/src/frontend/app/document/document.component.html b/src/frontend/app/document/document.component.html index fc611b7825318c81e7abc248f98d88263250d5d9..80c9e44e70a9e6a528690d49594f23e14f022d34 100755 --- a/src/frontend/app/document/document.component.html +++ b/src/frontend/app/document/document.component.html @@ -111,16 +111,9 @@ </ion-content> <ion-footer *ngIf="!loadingImage && currentDoc === 0" class="ion-no-border footer-buttons"> - <ion-button color="danger" shape="round" size="large" fill="outline" (click)="refuseDocument()"> - <ion-icon [slot]="'start'" name="thumbs-down-outline"></ion-icon> - <ion-label style="font-size: 13px;">{{'lang.reject' | translate}}</ion-label> - </ion-button> - <ion-button size="large" shape="round" fill="outline" (click)="openSignatures()"> - <ion-label style="font-size: 13px;">{{'lang.signatures' | translate}}</ion-label> - </ion-button> - <ion-button color="success" shape="round" size="large" fill="outline" (click)="validateDocument()"> - <ion-icon [slot]="'start'" name="thumbs-up-outline"></ion-icon> - <ion-label style="font-size: 13px;">{{'lang.validate' | translate}}</ion-label> + <ion-button *ngFor="let action of actionsList" [color]="action.color" shape="round" size="large" fill="outline" (click)="launchEvent(action)"> + <ion-icon *ngIf="action.logo !== ''" [slot]="'start'" [name]="action.logo"></ion-icon> + <ion-label style="font-size: 13px;">{{action.label | translate}}</ion-label> </ion-button> </ion-footer> diff --git a/src/frontend/app/document/document.component.ts b/src/frontend/app/document/document.component.ts index 1407dad9b630a1c3fa259caadc890aedd949689e..a054df17d38a00dc41b9b785b4993389dd1e2469 100755 --- a/src/frontend/app/document/document.component.ts +++ b/src/frontend/app/document/document.component.ts @@ -20,7 +20,7 @@ import { AuthService } from '../service/auth.service'; import { LocalStorageService } from '../service/local-storage.service'; import { ActionSheetController, AlertController, LoadingController, MenuController, ModalController } from '@ionic/angular'; import { NgxExtendedPdfViewerService } from 'ngx-extended-pdf-viewer'; -import { catchError, tap } from 'rxjs/operators'; +import { catchError, finalize, tap } from 'rxjs/operators'; import { of } from 'rxjs'; @@ -42,22 +42,22 @@ export class DocumentComponent implements OnInit { { id: 2, label: 'lang.reject', - color: '#e74c3c', - logo: 'fas fa-backspace', + color: 'danger', + logo: 'thumbs-down-outline', event: 'refuseDocument' }, { id: 3, label: 'lang.signatures', - color: '#135F7F', + color: '', logo: '', - event: 'openDrawer' + event: 'openSignatures' }, { id: 1, label: 'lang.validate', - color: '#2ecc71', - logo: 'fas fa-check-circle', + color: 'success', + logo: 'thumbs-up-outline', event: 'validateDocument' }, ]; @@ -306,6 +306,19 @@ export class DocumentComponent implements OnInit { this.http.get('../rest/documents/' + params['id']).pipe( tap((data: any) => { this.mainDocument = data.document; + + this.mainDocument.workflow = this.mainDocument.workflow.map((item: any) => { + return { + ...item, + 'role': item.mode === 'visa' ? 'visa' : item.signatureMode, + 'modes': [ + 'visa', + 'sign', + 'stamp', + ] + }; + }); + this.totalPages = this.mainDocument.pages; this.signaturesService.mainDocumentId = this.mainDocument.id; @@ -575,12 +588,22 @@ export class DocumentComponent implements OnInit { { text: this.translate.instant('lang.rejectDocument'), handler: () => { - const config: MatBottomSheetConfig = { - disableClose: true, - direction: 'ltr' - }; - this.bottomSheet.open(RejectInfoBottomSheetComponent, config); - this.localStorage.remove(this.mainDocument.id.toString()); + this.loadingController.create({ + message: 'Envoi ...', + spinner: 'dots' + }).then(async (load: HTMLIonLoadingElement) => { + load.present(); + const res = await this.sendDocument(); + if (res) { + const config: MatBottomSheetConfig = { + disableClose: true, + direction: 'ltr' + }; + this.bottomSheet.open(RejectInfoBottomSheetComponent, config); + this.localStorage.remove(this.mainDocument.id.toString()); + } + load.dismiss(); + }); } } ] @@ -597,13 +620,22 @@ export class DocumentComponent implements OnInit { { text: this.translate.instant('lang.validate'), handler: () => { - const config: MatBottomSheetConfig = { - disableClose: true, - direction: 'ltr' - }; - - this.bottomSheet.open(SuccessInfoValidBottomSheetComponent, config); - this.localStorage.remove(this.mainDocument.id.toString()); + this.loadingController.create({ + message: 'Envoi ...', + spinner: 'dots' + }).then(async (load: HTMLIonLoadingElement) => { + load.present(); + const res = await this.sendDocument(); + if (res) { + const config: MatBottomSheetConfig = { + disableClose: true, + direction: 'ltr' + }; + this.bottomSheet.open(SuccessInfoValidBottomSheetComponent, config); + this.localStorage.remove(this.mainDocument.id.toString()); + } + load.dismiss(); + }); } } ] @@ -612,6 +644,64 @@ export class DocumentComponent implements OnInit { await alert.present(); } + sendDocument() { + return new Promise((resolve) => { + const signatures: any[] = []; + if (this.signaturesService.currentAction > 0) { + for (let index = 1; index <= this.signaturesService.totalPage; index++) { + if (this.signaturesService.signaturesContent[index]) { + this.signaturesService.signaturesContent[index].forEach((signature: any) => { + signatures.push( + { + 'encodedImage': signature.encodedSignature, + 'width': signature.width, + 'positionX': signature.positionX, + 'positionY': signature.positionY, + 'type': 'PNG', + 'page': index, + } + ); + }); + } + if (this.signaturesService.notesContent[index]) { + this.signaturesService.notesContent[index].forEach((note: any) => { + signatures.push( + { + 'encodedImage': note.fullPath.replace('data:image/png;base64,', ''), + 'width': note.width, + 'positionX': note.positionX, + 'positionY': note.positionY, + 'type': 'PNG', + 'page': index, + } + ); + }); + } + } + + this.http.put('../rest/documents/' + this.signaturesService.mainDocumentId + '/actions/' + this.signaturesService.currentAction, { 'signatures': signatures }) + .pipe( + tap(() => { + if (this.signaturesService.documentsList[this.signaturesService.indexDocumentsList] !== undefined) { + this.signaturesService.documentsList.splice(this.signaturesService.indexDocumentsList, 1); + if (this.signaturesService.documentsListCount.current > 0) { + this.signaturesService.documentsListCount.current--; + } + } + resolve(true); + }), + catchError((err: any) => { + this.notificationService.handleErrors(err); + resolve(false); + return of(false); + }) + ).subscribe(); + } else { + resolve(false); + } + }); + } + openDrawer(): void { if (this.currentDoc > 0) { this.currentDoc = 0; 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 index 088d8ef75c9081ea8a7e60eed989fb7250a0525c..7c23daed0d693c7206395f9053eecb1b75eeb526 100644 --- 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 @@ -4,6 +4,7 @@ import { AlertController, PopoverController } from '@ionic/angular'; import { NotificationService } from '../../../service/notification.service'; import { catchError, tap } from 'rxjs/operators'; import { of } from 'rxjs'; +import { AuthService } from '../../../service/auth.service'; @Component({ selector: 'app-visa-workflow-models', @@ -21,6 +22,7 @@ export class VisaWorkflowModelsComponent implements OnInit { public popoverController: PopoverController, public alertController: AlertController, public notificationService: NotificationService, + public authService: AuthService ) { } ngOnInit(): void { @@ -43,9 +45,7 @@ export class VisaWorkflowModelsComponent implements OnInit { text: 'Cancel', role: 'cancel', cssClass: 'secondary', - handler: (blah) => { - console.log('Confirm Cancel: blah'); - } + handler: () => { } }, { text: 'Ok', handler: (data: any) => { @@ -70,8 +70,8 @@ export class VisaWorkflowModelsComponent implements OnInit { items: this.currentWorkflow.map((item: any) => { return { userId: item.userId, - mode: item.mode, - signatureMode: 'standard' + mode: this.authService.getWorkflowMode(item.role), + signatureMode: this.authService.getSignatureMode(item.role) }; }) }; @@ -137,13 +137,13 @@ export class VisaWorkflowModelsComponent implements OnInit { const obj: any = { 'userId': item.userId, 'userDisplay': item.userLabel, - 'mode': item.mode, + 'role': item.mode === 'visa' ? 'visa' : item.signatureMode, 'processDate': null, 'current': false, 'modes': [ 'visa', 'sign', - 'rgs' + 'stamp', ] }; return obj; 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 46b4ce5280c8f70d3ec6e17cdc9b62dafb8ae933..806201f191aea93c3eaa2fe224123b0783b89c59 100644 --- a/src/frontend/app/document/visa-workflow/visa-workflow.component.html +++ b/src/frontend/app/document/visa-workflow/visa-workflow.component.html @@ -21,7 +21,7 @@ </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;"> + style="position: absolute;z-index: 2;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> @@ -35,20 +35,35 @@ <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"> - <ion-item class="no-ripple" *ngFor="let diffusion of visaWorkflow;let i=index" dnd-sortable [sortableIndex]="i"> - <ion-avatar slot="start" class="avatar-user" style="cursor: grab;" (click)="$event.stopPropagation();"> + <ion-reorder-group (ionItemReorder)="doReorder($event)" [disabled]="!editMode"> + <ion-item class="no-ripple" *ngFor="let diffusion of visaWorkflow;let i=index" + [class.current]="diffusion.current"> + <ion-reorder slot="start"></ion-reorder> + <ion-avatar *ngIf="!editMode" slot="start" class="avatar-user" style="cursor: grab;" + (click)="$event.stopPropagation();"> <img [src]="diffusion.userPicture"> </ion-avatar> - <ion-label>{{diffusion.userDisplay}}</ion-label> - <ion-select [(ngModel)]="diffusion.mode" interface="popover" [interfaceOptions]="customPopoverOptions" [style.color]="getRole(diffusion.mode).color"> - <ion-select-option [value]="mode" *ngFor="let mode of diffusion.modes"> - {{getRole(mode).label}} - </ion-select-option> - </ion-select> - <ion-button fill="clear" slot="end" shape="round" color="danger" (click)="removeUser(i)"> + <ion-label> + <p *ngIf="diffusion.processDate === null" style="display: flex;justify-content: start;"> + <ion-select [(ngModel)]="diffusion.role" + [title]="'lang.' + diffusion.role + 'User' | translate" interface="popover" + [interfaceOptions]="customPopoverOptions" [style.color]="getRole(diffusion.role).color" + [disabled]="!editMode" style="width: auto;max-width: 100%;padding-left:0px;"> + <ion-select-option [value]="mode" *ngFor="let mode of diffusion.modes"> + {{'lang.' + mode + 'User' | translate}} + </ion-select-option> + </ion-select> + </p> + <p *ngIf="diffusion.processDate !== null" [title]="diffusion.processDate" + class="processDate"> + {{'lang.'+diffusion.mode+'ProcessInfo' | translate}} + {{diffusion.processDate}} + </p> + <h2 [title]="diffusion.userDisplay">{{diffusion.userDisplay}}</h2> + </ion-label> + <ion-button *ngIf="editMode" fill="clear" slot="end" shape="round" color="danger" (click)="removeUser(i)"> <ion-icon slot="icon-only" name="trash-outline"></ion-icon> </ion-button> </ion-item> - </ion-list> + </ion-reorder-group> </ion-content> \ No newline at end of file diff --git a/src/frontend/app/document/visa-workflow/visa-workflow.component.scss b/src/frontend/app/document/visa-workflow/visa-workflow.component.scss index b2393eccb8b5d42241a3d769676ac5d95a9f10be..d0ffa7ea274588cc4d4a2dcb801a735f649b32a6 100644 --- a/src/frontend/app/document/visa-workflow/visa-workflow.component.scss +++ b/src/frontend/app/document/visa-workflow/visa-workflow.component.scss @@ -1,6 +1,5 @@ - .current { - --background: var(--ion-color-success); + border-left: solid 5px var(--ion-color-success); } .primary { @@ -32,4 +31,23 @@ ion-select { .no-ripple { --ripple-color: transparent; - } \ No newline at end of file +} + +.select-disabled, +.item-select-disabled ion-label { + opacity: 1; +} + +.item-select-disabled ion-select::part(icon) { + display: none !important; +} + +.processDate { + display: flex; + justify-content: start; + font-size: 12px; + text-align: right; + color : var(--ion-color-success); + padding-top: 10px; + padding-bottom: 10px; +} \ No newline at end of file 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 b584f47f301e8e50951cba285a8b0b9fa2ef6160..279d35af167fdf8aab7834d68e1a6e8f8b829a3b 100644 --- a/src/frontend/app/document/visa-workflow/visa-workflow.component.ts +++ b/src/frontend/app/document/visa-workflow/visa-workflow.component.ts @@ -1,12 +1,14 @@ -import { Component, Input, OnInit } from '@angular/core'; +import { Component, Input, OnInit, ViewChild } from '@angular/core'; import { SignaturesContentService } from '../../service/signatures.service'; import { HttpClient } from '@angular/common/http'; 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 { IonReorderGroup, PopoverController } from '@ionic/angular'; import { VisaWorkflowModelsComponent } from './models/visa-workflow-models.component'; +import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop'; +import { ItemReorderEventDetail } from '@ionic/core'; @Component({ selector: 'app-visa-workflow', @@ -23,29 +25,11 @@ export class VisaWorkflowComponent implements OnInit { customPopoverOptions = { header: 'Roles' }; - roles: any[] = [ - { - id: 'rgs', - type: 'sign', - label: 'Signataire (RGS**)', - color: '#cb4335' - }, - { - id: 'stamp', - type: 'sign', - label: 'Signataire (Griffe)', - color: '#f39c12' - }, - { - id: 'visa', - type: 'visa', - label: 'Viseur', - color: '#27ae60' - }, - ]; + roles: any[] = []; @Input() editMode: boolean = false; @Input() visaWorkflow: any = []; + @ViewChild(IonReorderGroup) reorderGroup: IonReorderGroup; constructor( public http: HttpClient, @@ -55,17 +39,31 @@ export class VisaWorkflowComponent implements OnInit { public popoverController: PopoverController ) { } + ngOnInit(): void { - this.visaWorkflow.forEach((element: any) => { - if (element.userPicture === undefined && element.userDisplay !== '') { - this.http.get('../rest/users/' + element.userId + '/picture') - .subscribe((data: any) => { - element.userPicture = data.picture; - }); - } + this.visaWorkflow.forEach((element: any, index: number) => { + this.getAvatarUser(index); }); } + doReorder(ev: CustomEvent<ItemReorderEventDetail>) { + // Before complete is called with the items they will remain in the + // order before the drag + console.log('Before complete', this.visaWorkflow); + + // Finish the reorder and position the item in the DOM based on + // where the gesture ended. Update the items variable to the + // new order of items + this.visaWorkflow = ev.detail.complete(this.visaWorkflow); + + // After complete is called the items will be in the new order + console.log('After complete', this.visaWorkflow); + } + + drop(event: CdkDragDrop<string[]>) { + moveItemInArray(this.visaWorkflow, event.previousIndex, event.currentIndex); + } + getVisaUsers(ev: any) { this.showVisaUsersList = true; if (ev.detail.value === '') { @@ -85,17 +83,16 @@ export class VisaWorkflowComponent implements OnInit { addUser(user: any, searchInput: any) { this.resetVisaUsersList(); + + user.signatureModes.unshift('visa'); + const userObj: any = { 'userId': user.id, 'userDisplay': `${user.firstname} ${user.lastname}`, - 'mode': 'visa', + 'role': 'visa', 'processDate': null, 'current': false, - 'modes': [ - 'visa', - 'stamp', - 'rgs' - ] + 'modes': user.signatureModes }; this.visaWorkflow.push(userObj); this.getAvatarUser(this.visaWorkflow.length - 1); @@ -150,6 +147,6 @@ export class VisaWorkflowComponent implements OnInit { } getRole(id: string) { - return this.roles.filter((mode: any) => mode.id === id)[0]; + return this.authService.signatureRoles.filter((mode: any) => mode.id === id)[0]; } } diff --git a/src/frontend/app/indexation/indexation.component.html b/src/frontend/app/indexation/indexation.component.html index 45c7c8702603c8207096a1897499d3e4d3acb71c..1c615f061d5768b7d812f45b2ef664b1aa3d1e81 100644 --- a/src/frontend/app/indexation/indexation.component.html +++ b/src/frontend/app/indexation/indexation.component.html @@ -7,41 +7,43 @@ </ion-toolbar> </ion-header> <ion-content> - <input type="file" #docToUpload name="files[]" (change)="uploadTrigger($event)" style="display:none;" multiple> - <div class="dnd-area" appUploadFileDragDrop (onFileDropped)="dndUploadFile($event)"> - <ion-button fill="outline" color="medium" size="large" (click)="docToUpload.click()" > - {{'lang.chooseDocuments' | translate}} - </ion-button> - <ion-item lines="none" class="ion-text-center no-background"> - <ion-label color="medium"> - {{'lang.dndDocuments' | translate}} - </ion-label> - </ion-item> + <div style="display: flex;flex-direction: column;height: 100%;"> + <ion-list *ngIf="filesToUpload.length > 0"> + <ion-list-header> + <ion-label color="primary">{{'lang.documentsToUpload' | translate}}</ion-label> + </ion-list-header> + <ion-item *ngFor="let file of filesToUpload;let i=index"> + <ion-buttons slot="start"> + <ion-button fill="clear" slot="icon-only" shape="round" color="primary" + [title]="file.mainDocument ? 'Document à signer' : 'Pièce jointe'" + (click)="file.mainDocument=!file.mainDocument"> + <ion-icon *ngIf="file.mainDocument" slot="icon-only" name="pencil-outline"></ion-icon> + <ion-icon *ngIf="!file.mainDocument" slot="icon-only" name="document-attach-outline"></ion-icon> + </ion-button> + </ion-buttons> + <ion-icon *ngIf="file.type === 'attachment'" color="primary" slot="start" name="document-attach-outline" + title="Pièce jointe"></ion-icon> + <ion-input placeholder="Nom du fichier" matInput type="text" [(ngModel)]="file.title"></ion-input> + <ion-buttons slot="end"> + <ion-button fill="clear" slot="icon-only" shape="round" color="danger" + [title]="'lang.delete' | translate" (click)="deleteFile(i)"> + <ion-icon slot="icon-only" name="trash-outline"></ion-icon> + </ion-button> + </ion-buttons> + </ion-item> + </ion-list> + <input type="file" #docToUpload name="files[]" (change)="uploadTrigger($event)" style="display:none;" multiple> + <div class="dnd-area" appUploadFileDragDrop (onFileDropped)="dndUploadFile($event)"> + <ion-button fill="outline" color="medium" size="large" (click)="docToUpload.click()"> + {{'lang.chooseDocuments' | translate}} + </ion-button> + <ion-item lines="none" class="ion-text-center no-background"> + <ion-label color="medium"> + {{'lang.dndDocuments' | translate}} + </ion-label> + </ion-item> + </div> </div> - <ion-list *ngIf="filesToUpload.length > 0"> - <ion-list-header> - <ion-label color="primary">{{'lang.documentsToUpload' | translate}}</ion-label> - </ion-list-header> - <ion-item *ngFor="let file of filesToUpload;let i=index"> - <ion-buttons slot="start"> - <ion-button fill="clear" slot="icon-only" shape="round" color="primary" - [title]="file.mainDocument ? 'Document à signer' : 'Pièce jointe'" - (click)="file.mainDocument=!file.mainDocument"> - <ion-icon *ngIf="file.mainDocument" slot="icon-only" name="pencil-outline"></ion-icon> - <ion-icon *ngIf="!file.mainDocument" slot="icon-only" name="document-attach-outline"></ion-icon> - </ion-button> - </ion-buttons> - <ion-icon *ngIf="file.type === 'attachment'" color="primary" slot="start" name="document-attach-outline" - title="Pièce jointe"></ion-icon> - <ion-input placeholder="Nom du fichier" matInput type="text" [(ngModel)]="file.title"></ion-input> - <ion-buttons slot="end"> - <ion-button fill="clear" slot="icon-only" shape="round" color="danger" - [title]="'lang.delete' | translate" (click)="deleteFile(i)"> - <ion-icon slot="icon-only" name="trash-outline"></ion-icon> - </ion-button> - </ion-buttons> - </ion-item> - </ion-list> </ion-content> <ion-footer class="ion-no-border"> <div class="ion-text-center" style="background: white;"> diff --git a/src/frontend/app/indexation/indexation.component.scss b/src/frontend/app/indexation/indexation.component.scss index 5eec1a13227ed4bd187dd4ce886c5563fc9413a0..4527d0a17bace5fa25cbbc6d544b57b6c2173d8b 100644 --- a/src/frontend/app/indexation/indexation.component.scss +++ b/src/frontend/app/indexation/indexation.component.scss @@ -8,6 +8,11 @@ color: gray; padding-top: 50px; padding-bottom: 50px; + flex: 1; + align-items: center; + display: flex; + flex-direction: column; + justify-content: center; } .no-background { diff --git a/src/frontend/app/indexation/indexation.component.ts b/src/frontend/app/indexation/indexation.component.ts index 9799c512b79c85a6e346b5c74ab8c527ffb1dd39..02fc646283a0fc69cc7f4745c75a5249bcd19efc 100644 --- a/src/frontend/app/indexation/indexation.component.ts +++ b/src/frontend/app/indexation/indexation.component.ts @@ -1,7 +1,9 @@ +import { DatePipe } from '@angular/common'; import { HttpClient } from '@angular/common/http'; import { Component, OnInit, TemplateRef, ViewChild, ViewContainerRef } from '@angular/core'; import { Router } from '@angular/router'; -import { LoadingController, MenuController } from '@ionic/angular'; +import { AlertController, LoadingController, MenuController } from '@ionic/angular'; +import { TranslateService } from '@ngx-translate/core'; import { of } from 'rxjs'; import { catchError, finalize, tap } from 'rxjs/operators'; import { VisaWorkflowComponent } from '../document/visa-workflow/visa-workflow.component'; @@ -12,6 +14,7 @@ import { SignaturesContentService } from '../service/signatures.service'; @Component({ templateUrl: 'indexation.component.html', styleUrls: ['indexation.component.scss'], + providers: [DatePipe] }) export class IndexationComponent implements OnInit { @@ -24,6 +27,7 @@ export class IndexationComponent implements OnInit { constructor( public http: HttpClient, + private translate: TranslateService, public router: Router, private menu: MenuController, public signaturesService: SignaturesContentService, @@ -31,6 +35,8 @@ export class IndexationComponent implements OnInit { public notificationService: NotificationService, public authService: AuthService, public loadingController: LoadingController, + public alertController: AlertController, + public datePipe: DatePipe, ) { } ngOnInit(): void { @@ -50,24 +56,55 @@ export class IndexationComponent implements OnInit { onSubmit() { if (this.isValid()) { - this.loadingController.create({ - message: 'Enregistrement ...', - spinner: 'dots' - }).then(async (load: HTMLIonLoadingElement) => { - load.present(); - const objTosend = this.formatData(); - for (let index = 0; index < objTosend.length; index++) { - await this.saveDocument(objTosend[index], index); - } - load.dismiss(); - if (this.errors.length === 0) { - this.notificationService.success('Document(s) importé(s)'); - this.router.navigate(['/home']); - } - }); + this.promptSaveDoc(); } } + async promptSaveDoc() { + const alert = await this.alertController.create({ + cssClass: 'custom-alert-info', + header: this.translate.instant('lang.warning'), + message: this.translate.instant('lang.areYouSure'), + inputs: [ + { + name: 'note', + id: 'note', + type: 'textarea', + placeholder: this.translate.instant('lang.addNote') + }, + ], + buttons: [ + { + text: this.translate.instant('lang.cancel'), + role: 'cancel', + cssClass: 'secondary', + handler: () => { } + }, + { + text: this.translate.instant('lang.validate'), + handler: (data: any) => { + this.loadingController.create({ + message: 'Enregistrement ...', + spinner: 'dots' + }).then(async (load: HTMLIonLoadingElement) => { + load.present(); + const objTosend = this.formatData(data.note); + for (let index = 0; index < objTosend.length; index++) { + await this.saveDocument(objTosend[index], index); + } + load.dismiss(); + if (this.errors.length === 0) { + this.notificationService.success('Document(s) importé(s)'); + this.router.navigate(['/home']); + } + }); + } + } + ] + }); + await alert.present(); + } + saveDocument(data: any, index: number) { return new Promise((resolve) => { this.http.post('../rest/documents', data).pipe( @@ -83,7 +120,17 @@ export class IndexationComponent implements OnInit { }); } - formatData() { + formatData(note: string) { + let noteObj: any = null; + + if (note !== '') { + const today: Date = new Date(); + noteObj = { + value : note, + creator : `${this.authService.user.firstname} ${this.authService.user.lastname}`, + creationDate : this.datePipe.transform(today, 'dd-MM-y') + }; + } const formattedObj: any[] = []; const signedFiles = this.filesToUpload.filter((item: any) => item.mainDocument); const attachFiles = this.filesToUpload.filter((item: any) => !item.mainDocument); @@ -94,6 +141,7 @@ export class IndexationComponent implements OnInit { encodedDocument: file.content, isZipped: false, sender: `${this.authService.user.firstname} ${this.authService.user.lastname}`, + notes: noteObj, attachments: attachFiles.map((item: any) => { return { title: item.title, @@ -103,7 +151,8 @@ export class IndexationComponent implements OnInit { workflow: this.appVisaWorkflow.getCurrentWorkflow().map((item: any) => { return { userId: item.userId, - mode: item.mode + mode: this.authService.getWorkflowMode(item.role), + signatureMode: this.authService.getSignatureMode(item.role) }; }) }); diff --git a/src/frontend/app/service/auth.guard.ts b/src/frontend/app/service/auth.guard.ts index 1fa6a1f693097521c362e396109e7a78115c26bb..744b8e64091636ab3f0cb2d2aba27a8eb3bb7253 100644 --- a/src/frontend/app/service/auth.guard.ts +++ b/src/frontend/app/service/auth.guard.ts @@ -60,6 +60,34 @@ export class AuthGuard implements CanActivate { this.authService.user.picture = dataPic.picture; }); } + + this.authService.signatureRoles = [ + { + 'id': 'rgs_2stars', + 'type': 'sign', + 'color': '#FF0000' + }, + { + 'id': 'inca_card', + 'type': 'sign', + 'color': '#FFA500' + }, + { + 'id': 'eidas', + 'type': 'sign', + 'color': '#00FF00' + }, + { + 'id': 'stamp', + 'type': 'sign', + 'color': '#808080' + }, + { + 'id': 'visa', + 'type': 'visa', + 'color': '#135F7F' + } + ]; } return true; @@ -67,6 +95,34 @@ export class AuthGuard implements CanActivate { return this.http.get('../rest/authenticationInformations') .pipe( map((data: any) => { + // FOR TEST + this.authService.signatureRoles = [ + { + 'id': 'rgs_2stars', + 'type': 'sign', + 'color': '#FF0000' + }, + { + 'id': 'inca_card', + 'type': 'sign', + 'color': '#FFA500' + }, + { + 'id': 'eidas', + 'type': 'sign', + 'color': '#00FF00' + }, + { + 'id': 'stamp', + 'type': 'sign', + 'color': '#808080' + }, + { + 'id': 'visa', + 'type': 'visa', + 'color': '#135F7F' + } + ]; this.authService.authMode = data.connection; this.authService.changeKey = data.changeKey; this.localStorage.setAppSession(data.instanceId); diff --git a/src/frontend/app/service/auth.service.ts b/src/frontend/app/service/auth.service.ts index 0a68b73f6dfa213d6429d6aeef6a22994714b685..21d87a5ac13bd4042855eab782043c208c1d20ad 100755 --- a/src/frontend/app/service/auth.service.ts +++ b/src/frontend/app/service/auth.service.ts @@ -14,6 +14,7 @@ export class AuthService { authMode: string = 'default'; changeKey: boolean = false; user: any = {}; + signatureRoles: any[] = []; constructor(public http: HttpClient, private router: Router, @@ -87,4 +88,12 @@ export class AuthService { setUser(value: any) { this.user = value; } + + getSignatureMode(id: string) { + return id === 'visa' ? 'stamp' : id; + } + + getWorkflowMode(id: string) { + return this.signatureRoles.filter((item: any) => item.id === id)[0].type; + } }