Skip to content
Snippets Groups Projects
Commit aa4e3451 authored by Alex ORLUC's avatar Alex ORLUC
Browse files

FEAT #16982 TIME 4 WIP front otp circuit

parent 8772432e
No related branches found
No related tags found
No related merge requests found
Showing
with 418 additions and 194 deletions
......@@ -473,6 +473,12 @@
"mailServerOffline": "Le serveur de messagerie est indisponible pour le moment, veuillez ré-essayer ultérieurement",
"mailServerOfflineMsg": "Le serveur de messagerie doit être <b>testé</b> et <b>opérationnel</b> pour : <b>{{action}}</b>",
"testAndValidate": "Tester et valider",
"receiveActivationNotification": "Recevoir le courriel d'activation"
"receiveActivationNotification": "Recevoir le courriel d'activation",
"newOtp": "Ajouter un OTP",
"otpUser": "Utilisateur OTP",
"notifyUserBy": "Notifier l'utilisateur par",
"phoneAlt": "Mobile",
"sms": "Sms",
"source": "Source"
}
}
......@@ -53,13 +53,14 @@ import { MainDocumentDetailComponent } from './document/main-document-detail/mai
import { AssociatedDocumentsComponent } from './document/associated-documents/associated-documents.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';
import { SearchComponent } from './search/search.component';
import { SignaturePositionComponent } from './indexation/signature-position/signature-position.component';
import { DevToolComponent } from './service/debug/dev-tool.component';
import { DevLangComponent } from './service/debug/dev-lang.component';
import { DocumentDateListComponent } from './documentDateList/document-date-list.component';
import { DateOptionModalComponent } from './documentDateList/dateOption/date-option-modal.component';
import { OtpCreateComponent } from './document/visa-workflow/otps/otp-create.component';
import { OtpYousignComponent } from './document/visa-workflow/otps/yousign/otp-yousign.component';
// ADMINISTRATION
......@@ -140,7 +141,6 @@ registerLocaleData(localeFr, 'fr-FR');
CheckConnectionComponent,
CheckEmailConnectionComponent,
IndexationComponent,
VisaWorkflowModelsComponent,
SearchComponent,
SignaturePositionComponent,
DevToolComponent,
......@@ -148,7 +148,9 @@ registerLocaleData(localeFr, 'fr-FR');
SignatureMethodModalComponent,
HistoryListComponent,
DocumentDateListComponent,
DateOptionModalComponent
DateOptionModalComponent,
OtpCreateComponent,
OtpYousignComponent
],
imports: [
FormsModule,
......
<ion-list lines="none">
<ion-list-header>
<ion-label color="secondary">{{'lang.circuitModels' | translate}}</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">{{'lang.newTemplate' | translate}}</ion-label>
</ion-item>
</ion-list>
\ No newline at end of file
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';
import { AuthService } from '../../../service/auth.service';
import { TranslateService } from '@ngx-translate/core';
@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,
private translate: TranslateService,
public popoverController: PopoverController,
public alertController: AlertController,
public notificationService: NotificationService,
public authService: AuthService
) { }
ngOnInit(): void {
this.getVisaUserModels();
}
async createModel() {
const alert = await this.alertController.create({
header: this.translate.instant('lang.newTemplate'),
message: this.translate.instant('lang.newTemplateDesc'),
inputs: [
{
name: 'title',
type: 'text',
placeholder: this.translate.instant('lang.label') + ' *',
},
],
buttons: [
{
text: this.translate.instant('lang.cancel'),
role: 'cancel',
handler: () => { }
}, {
text: this.translate.instant('lang.validate'),
handler: (data: any) => {
if (data.title !== '') {
this.saveModel(data.title);
return true;
} else {
this.notificationService.error(this.translate.instant('lang.label') + ' ' + this.translate.instant('lang.mandatory'));
return false;
}
}
}
]
});
await alert.present();
}
saveModel(title: string) {
const objToSend: any = {
title: title,
items: this.currentWorkflow.map((item: any) => ({
userId: item.userId,
mode: this.authService.getWorkflowMode(item.role),
signatureMode: this.authService.getSignatureMode(item.role)
}))
};
this.http.post('../rest/workflowTemplates', objToSend).pipe(
tap((res: any) => {
this.notificationService.success('lang.modelCreated');
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: this.translate.instant('lang.delete'),
message: this.translate.instant('lang.deleteTemplate'),
buttons: [
{
text: this.translate.instant('lang.no'),
role: 'cancel',
handler: () => { }
}, {
text: this.translate.instant('lang.yes'),
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,
'role': item.mode === 'visa' ? 'visa' : item.signatureMode,
'processDate': null,
'current': false,
'modes': ['visa'].concat(item.userSignatureModes)
};
return obj;
});
this.popoverController.dismiss(workflows);
}),
catchError(err => {
this.notificationService.handleErrors(err);
return of(false);
})
).subscribe();
}
}
<ion-header [translucent]="true">
<ion-toolbar color="primary">
<ion-title>{{'lang.otpUser' | translate}}</ion-title>
<ion-buttons slot="end">
<ion-button (click)="dismissModal()">
<ion-icon slot="icon-only" name="close-outline"></ion-icon>
</ion-button>
</ion-buttons>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-card>
<ion-item>
<ion-label color="secondary">{{'lang.source' | translate}}</ion-label>
<ion-select [value]="currentSource.id" cancelText="{{'lang.cancel' | translate}}">
<ion-select-option *ngFor="let source of sources" [value]="source.id">
{{source.label | translate}}</ion-select-option>
</ion-select>
</ion-item>
</ion-card>
<app-otp-yousign #appOtpYousign *ngIf="currentSource.type === 'yousign'"></app-otp-yousign>
</ion-content>
<ion-footer class="ion-no-border">
<ion-toolbar>
<ion-buttons class="ion-justify-content-center">
<ion-button type="submit" color="primary" (click)="onSubmit()" [disabled]="!isValid()">
<ion-label>{{'lang.save' | translate}}</ion-label>
</ion-button>
</ion-buttons>
</ion-toolbar>
</ion-footer>
\ No newline at end of file
.my-custom-class {
--min-width: 400px;
}
\ No newline at end of file
import { Component, OnInit, ViewChild } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { NotificationService } from '../../../service/notification.service';
import { AuthService } from '../../../service/auth.service';
import { TranslateService } from '@ngx-translate/core';
import { ModalController } from '@ionic/angular';
import { OtpYousignComponent } from './yousign/otp-yousign.component';
@Component({
selector: 'app-otp-create',
templateUrl: 'otp-create.component.html',
styleUrls: ['otp-create.component.scss'],
})
export class OtpCreateComponent implements OnInit {
@ViewChild('appOtpYousign', { static: false }) appOtpYousign: OtpYousignComponent;
sources: any[] = [];
currentSource: any = null;
constructor(
public http: HttpClient,
private translate: TranslateService,
public notificationService: NotificationService,
public authService: AuthService,
public modalController: ModalController
) { }
ngOnInit(): void {
this.getConfig();
}
getConfig() {
// FOR TEST
this.sources = [
{
id: 1,
label: 'yousign',
type: 'yousign'
}
];
this.currentSource = this.sources[0];
/* this.http.get(`../rest/???`).pipe(
tap((data: any) => {
}),
catchError(err => {
this.notificationService.handleErrors(err);
return of(false);
})
).subscribe();*/
}
onSubmit() {
this.modalController.dismiss(this.appOtpYousign.getData());
}
dismissModal() {
this.modalController.dismiss('cancel');
}
isValid() {
return this.appOtpYousign?.isValid();
}
}
<form style="display: contents;" #otpForm="ngForm">
<ion-list lines="full" class="ion-no-margin">
<ion-item>
<ion-label color="secondary" position="floating">{{'lang.firstname' | translate}} *</ion-label>
<ion-input name="firstname" [maxlength]="128" [(ngModel)]="otp.firstname" placeholder="Alain" required>
</ion-input>
</ion-item>
<ion-item>
<ion-label color="secondary" position="floating">{{'lang.lastname' | translate}} *</ion-label>
<ion-input name="lastname" [maxlength]="128" [(ngModel)]="otp.lastname" placeholder="Dupont" required>
</ion-input>
</ion-item>
<ion-item>
<ion-label color="secondary" position="floating">{{'lang.phoneAlt' | translate}} <ng-container
*ngIf="otp.mode === 'sms'">*</ng-container>
</ion-label>
<ion-input name="phone" pattern="^\+?[1-9]\d{1,14}$" [(ngModel)]="otp.phone" placeholder="+33646342143"
[required]="otp.mode === 'sms'"></ion-input>
</ion-item>
<ion-item>
<ion-label color="secondary" position="floating">{{'lang.email' | translate}} <ng-container
*ngIf="otp.mode === 'email'">*</ng-container>
</ion-label>
<ion-input name="email" pattern="(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)"
placeholder="alain.dupont@gmail.com" [(ngModel)]="otp.email" [required]="otp.mode === 'email'">
</ion-input>
</ion-item>
</ion-list>
<ion-list>
<ion-radio-group [(ngModel)]="otp.role" [value]="otp.role">
<ion-list-header>
<ion-label color="secondary">{{'lang.role' | translate}}</ion-label>
</ion-list-header>
<div style="display:flex;">
<ion-item *ngFor="let role of roles" style="flex:1;">
<ion-label>{{'lang.' + role | translate}}</ion-label>
<ion-radio slot="start" [value]="role"></ion-radio>
</ion-item>
</div>
</ion-radio-group>
<!--<ion-label color="secondary" position="floating">{{'lang.role' | translate}}</ion-label>
<ion-select name="mode" [(ngModel)]="otp.role" [value]="otp.role" cancelText="{{'lang.cancel' | translate}}">
<ion-select-option *ngFor="let role of roles" [value]="role">
{{'lang.' + role | translate}}</ion-select-option>
</ion-select>-->
</ion-list>
<ion-list>
<ion-item>
<ion-label color="secondary" position="floating">{{'lang.notifyUserBy' | translate}}</ion-label>
<ion-select name="mode" [(ngModel)]="otp.notify" [value]="otp.notify"
cancelText="{{'lang.cancel' | translate}}" [disabled]="notificationModes.length === 1">
<ion-select-option *ngFor="let mode of notificationModes" [value]="mode">
{{'lang.' + mode | translate}}</ion-select-option>
</ion-select>
</ion-item>
</ion-list>
</form>
\ No newline at end of file
.item-interactive-disabled.sc-ion-label-md-h:not(.item-multiple-inputs), .item-interactive-disabled:not(.item-multiple-inputs) .sc-ion-label-md-h {
opacity: initial !important;
}
.error {
font-size: 10px;
padding: 5px;
color: var(--ion-color-danger);
}
\ No newline at end of file
import { Component, OnInit, ViewChild } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { NotificationService } from '../../../../service/notification.service';
import { TranslateService } from '@ngx-translate/core';
import { NgForm } from '@angular/forms';
@Component({
selector: 'app-otp-yousign',
templateUrl: 'otp-yousign.component.html',
styleUrls: ['otp-yousign.component.scss'],
})
export class OtpYousignComponent implements OnInit {
@ViewChild('otpForm', { static: false }) otpForm: NgForm;
notificationModes: any[] = [];
roles: any[] = [
'otp_visa_yousign',
'otp_sign_yousign'
];
otp: any = {
firstname: '',
lastname: '',
email: '',
phone: '',
notify: 'sms',
role: 'otp_sign_yousign'
};
constructor(
public http: HttpClient,
private translate: TranslateService,
public notificationService: NotificationService,
) { }
ngOnInit(): void {
this.getConfig();
}
getConfig() {
// FOR TEST
this.notificationModes = [
'sms',
'email'
];
this.otp.mode = this.notificationModes[0];
/* this.http.get(`../rest/???`).pipe(
tap((data: any) => {
}),
catchError(err => {
this.notificationService.handleErrors(err);
return of(false);
})
).subscribe();*/
}
getData() {
return this.otp;
}
isValid() {
return this.otpForm.valid;
}
}
......@@ -14,11 +14,33 @@
<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]="'lang.circuitModels' | translate">
<ion-button fill="clear" slot="icon-only" shape="round" color="primary" [matMenuTriggerFor]="menu"
[title]="'lang.circuitModels' | translate">
<ion-icon slot="icon-only" name="albums-outline"></ion-icon>
</ion-button>
</ion-buttons>
<mat-menu #menu="matMenu">
<ion-item button (click)="openOtpModal()" lines="none">
<ion-icon name="person-circle-outline" slot="start" color="primary"></ion-icon>
<ion-label>{{'lang.newOtp' | translate}}</ion-label>
</ion-item>
<button mat-menu-item [matMenuTriggerFor]="model"
(menuOpened)="getVisaUserModels()">{{'lang.circuitModels' | translate}}</button>
</mat-menu>
<mat-menu #model="matMenu">
<ion-item button lines="none" *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]="visaWorkflow.length === 0">
<ion-label>{{'lang.newTemplate' | translate}}</ion-label>
</ion-item>
</mat-menu>
</ion-item>
<ion-list *ngIf="visaUsersList.length > 0"
style="position: absolute;z-index: 2;width: 100%;box-shadow: 0 4px 2px -2px gray;">
......@@ -41,8 +63,7 @@
<ion-reorder slot="start">
<ion-icon [title]="'lang.reorder' | translate" name="reorder-three" style="font-size: 30px;"></ion-icon>
</ion-reorder>
<ion-avatar slot="start" class="avatar-user" style="cursor: grab;"
(click)="$event.stopPropagation();">
<ion-avatar slot="start" class="avatar-user" style="cursor: grab;" (click)="$event.stopPropagation();">
<img [src]="diffusion.userPicture">
</ion-avatar>
<ion-label>
......@@ -56,7 +77,8 @@
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" [disabled]="!isValidRole(i,mode, diffusion.role)">
<ion-select-option [value]="mode" *ngFor="let mode of diffusion.modes"
[disabled]="!isValidRole(i,mode, diffusion.role)">
{{'lang.' + mode + 'User' | translate}}
</ion-select-option>
</ion-select>
......
......@@ -5,9 +5,10 @@ import { AuthService } from '../../service/auth.service';
import { catchError, tap } from 'rxjs/operators';
import { of } from 'rxjs';
import { NotificationService } from '../../service/notification.service';
import { IonReorderGroup, PopoverController } from '@ionic/angular';
import { VisaWorkflowModelsComponent } from './models/visa-workflow-models.component';
import { AlertController, IonReorderGroup, ModalController, PopoverController } from '@ionic/angular';
import { ItemReorderEventDetail } from '@ionic/core';
import { TranslateService } from '@ngx-translate/core';
import { OtpCreateComponent } from './otps/otp-create.component';
@Component({
selector: 'app-visa-workflow',
......@@ -29,13 +30,16 @@ export class VisaWorkflowComponent implements OnInit {
header: 'Roles'
};
roles: any[] = [];
visaWorkflowModels: any[] = [];
constructor(
public http: HttpClient,
private translate: TranslateService,
public alertController: AlertController,
public signaturesService: SignaturesContentService,
public authService: AuthService,
public notificationService: NotificationService,
public popoverController: PopoverController
public modalController: ModalController,
) { }
ngOnInit(): void {
......@@ -143,21 +147,26 @@ export class VisaWorkflowComponent implements OnInit {
this.visaUsersList = [];
}
async openVisaWorkflowModels(ev: any) {
const popover = await this.popoverController.create({
component: VisaWorkflowModelsComponent,
componentProps: { currentWorkflow: this.visaWorkflow },
event: ev,
async openOtpModal(ev: any) {
const modal = await this.modalController.create({
cssClass: 'my-custom-class',
component: OtpCreateComponent,
backdropDismiss: false,
});
await popover.present();
await modal.present();
popover.onDidDismiss()
modal.onDidDismiss()
.then((result: any) => {
if (result.role !== 'backdrop') {
this.visaWorkflow = this.visaWorkflow.concat(result.data);
this.visaWorkflow.forEach((element: any, index: number) => {
this.getAvatarUser(index);
});
if (typeof result.data === 'object') {
const obj: any = {
'userId': null,
'userDisplay': `${result.data.firstname} ${result.data.lastname}`,
'role': result.data.role,
'processDate': null,
'current': false,
'modes': [result.data.role]
};
this.visaWorkflow.push(obj);
}
});
}
......@@ -188,4 +197,127 @@ export class VisaWorkflowComponent implements OnInit {
return true;
}
}
async createModel() {
const alert = await this.alertController.create({
header: this.translate.instant('lang.newTemplate'),
message: this.translate.instant('lang.newTemplateDesc'),
inputs: [
{
name: 'title',
type: 'text',
placeholder: this.translate.instant('lang.label') + ' *',
},
],
buttons: [
{
text: this.translate.instant('lang.cancel'),
role: 'cancel',
handler: () => { }
}, {
text: this.translate.instant('lang.validate'),
handler: (data: any) => {
if (data.title !== '') {
this.saveModel(data.title);
return true;
} else {
this.notificationService.error(this.translate.instant('lang.label') + ' ' + this.translate.instant('lang.mandatory'));
return false;
}
}
}
]
});
await alert.present();
}
saveModel(title: string) {
const objToSend: any = {
title: title,
items: this.visaWorkflow.map((item: any) => ({
userId: item.userId,
mode: this.authService.getWorkflowMode(item.role),
signatureMode: this.authService.getSignatureMode(item.role)
}))
};
this.http.post('../rest/workflowTemplates', objToSend).pipe(
tap((res: any) => {
this.notificationService.success('lang.modelCreated');
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: this.translate.instant('lang.delete'),
message: this.translate.instant('lang.deleteTemplate'),
buttons: [
{
text: this.translate.instant('lang.no'),
role: 'cancel',
handler: () => { }
}, {
text: this.translate.instant('lang.yes'),
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();
}
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,
'role': item.mode === 'visa' ? 'visa' : item.signatureMode,
'processDate': null,
'current': false,
'modes': ['visa'].concat(item.userSignatureModes)
};
return obj;
});
this.visaWorkflow = this.visaWorkflow.concat(workflows);
this.visaWorkflow.forEach((element: any, index: number) => {
this.getAvatarUser(index);
});
}),
catchError(err => {
this.notificationService.handleErrors(err);
return of(false);
})
).subscribe();
}
getVisaUserModels() {
this.http.get('../rest/workflowTemplates').pipe(
tap((data: any) => {
this.visaWorkflowModels = data.workflowTemplates;
}),
catchError(err => {
this.notificationService.handleErrors(err);
return of(false);
})
).subscribe();
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment