From 0b5b00efcf1ff2c69dcf0720ea5eb2cfb6fca91c Mon Sep 17 00:00:00 2001 From: Alex ORLUC <alex.orluc@maarch.org> Date: Fri, 13 Sep 2019 17:16:13 +0200 Subject: [PATCH] FEAT #11269 TIME 8:30 add admin indexing model modification + begin indexing form mecanics --- apps/maarch_entreprise/xml/services.xml | 4 +- rest/index.php | 7 + .../administration-routing.module.ts | 8 +- .../administration/administration.module.ts | 4 +- ...exing-models-administration.component.html | 50 +++ ...exing-models-administration.component.scss | 14 + ...ndexing-models-administration.component.ts | 165 ++++++++++ src/frontend/app/app-common.module.ts | 12 +- .../field-list/field-list.component.html | 131 ++++++++ .../field-list/field-list.component.scss | 1 + .../field-list/field-list.component.ts | 57 ++++ .../indexing-form.component.html | 92 ++++++ .../indexing-form.component.scss | 241 ++++++++++++++ .../indexing-form/indexing-form.component.ts | 309 ++++++++++++++++++ src/frontend/lang/lang-en.ts | 10 + src/frontend/lang/lang-fr.ts | 10 + src/frontend/lang/lang-nl.ts | 10 + 17 files changed, 1118 insertions(+), 7 deletions(-) create mode 100644 src/frontend/app/administration/indexingModel/indexing-models-administration.component.html create mode 100644 src/frontend/app/administration/indexingModel/indexing-models-administration.component.scss create mode 100644 src/frontend/app/administration/indexingModel/indexing-models-administration.component.ts create mode 100644 src/frontend/app/indexation/field-list/field-list.component.html create mode 100644 src/frontend/app/indexation/field-list/field-list.component.scss create mode 100644 src/frontend/app/indexation/field-list/field-list.component.ts create mode 100644 src/frontend/app/indexation/indexing-form/indexing-form.component.html create mode 100644 src/frontend/app/indexation/indexing-form/indexing-form.component.scss create mode 100644 src/frontend/app/indexation/indexing-form/indexing-form.component.ts diff --git a/apps/maarch_entreprise/xml/services.xml b/apps/maarch_entreprise/xml/services.xml index e7627eef875..b2de61e9188 100755 --- a/apps/maarch_entreprise/xml/services.xml +++ b/apps/maarch_entreprise/xml/services.xml @@ -436,11 +436,11 @@ <id>admin_indexing_models</id> <name>_ADMIN_INDEXING_MODELS</name> <comment>_ADMIN_INDEXING_MODELS</comment> - <servicepage>/administration/indexing-models</servicepage> + <servicepage>/administration/indexingModels</servicepage> <servicetype>admin</servicetype> <category>organisation</category> <system_service>false</system_service> - <style>fa fa-user</style> + <style>fab fa-wpforms</style> <enabled>true</enabled> <angular>true</angular> </SERVICE> diff --git a/rest/index.php b/rest/index.php index 18bcc9dc0bf..0cd7ef9fac0 100755 --- a/rest/index.php +++ b/rest/index.php @@ -133,6 +133,13 @@ $app->post('/customFields', \CustomField\controllers\CustomFieldController::clas $app->put('/customFields/{id}', \CustomField\controllers\CustomFieldController::class . ':update'); $app->delete('/customFields/{id}', \CustomField\controllers\CustomFieldController::class . ':delete'); +//IndexingModels +$app->get('/indexingModels', \IndexingModel\controllers\IndexingModelController::class . ':get'); +$app->get('/indexingModels/{id}', \IndexingModel\controllers\IndexingModelController::class . ':getById'); +$app->post('/indexingModels', \IndexingModel\controllers\IndexingModelController::class . ':create'); +$app->put('/indexingModels/{id}', \IndexingModel\controllers\IndexingModelController::class . ':update'); +$app->delete('/indexingModels/{id}', \IndexingModel\controllers\IndexingModelController::class . ':delete'); + //Docservers $app->get('/docservers', \Docserver\controllers\DocserverController::class . ':get'); $app->post('/docservers', \Docserver\controllers\DocserverController::class . ':create'); diff --git a/src/frontend/app/administration/administration-routing.module.ts b/src/frontend/app/administration/administration-routing.module.ts index 72adae6a73e..f7fe4eea4a5 100755 --- a/src/frontend/app/administration/administration-routing.module.ts +++ b/src/frontend/app/administration/administration-routing.module.ts @@ -37,8 +37,9 @@ import { SecuritiesAdministrationComponent } from './security/securit import { SendmailAdministrationComponent } from './sendmail/sendmail-administration.component'; import { ShippingsAdministrationComponent } from './shipping/shippings-administration.component'; import { ShippingAdministrationComponent } from './shipping/shipping-administration.component'; -import { CustomFieldsAdministrationComponent } from './customField/custom-fields-administration.component'; -import { AppGuard } from '../../service/app.guard'; +import { CustomFieldsAdministrationComponent } from './customField/custom-fields-administration.component'; +import { AppGuard } from '../../service/app.guard'; +import { IndexingModelsAdministrationComponent } from './indexingModel/indexing-models-administration.component'; @NgModule({ imports: [ @@ -92,6 +93,9 @@ import { AppGuard } from '../../service/app.guard'; { path: 'administration/shippings/new', canActivate: [AppGuard], component: ShippingAdministrationComponent }, { path: 'administration/shippings/:id', canActivate: [AppGuard], component: ShippingAdministrationComponent }, { path: 'administration/customFields', canActivate: [AppGuard], component: CustomFieldsAdministrationComponent }, + /*{ path: 'administration/indexingModels', canActivate: [AppGuard], component: IndexingModelsAdministrationComponent },*/ + { path: 'administration/indexingModels/new', canActivate: [AppGuard], component: IndexingModelsAdministrationComponent }, + { path: 'administration/indexingModels/:id', canActivate: [AppGuard], component: IndexingModelsAdministrationComponent }, ]), ], exports: [ diff --git a/src/frontend/app/administration/administration.module.ts b/src/frontend/app/administration/administration.module.ts index 7a62b18c912..8a82938fdfb 100755 --- a/src/frontend/app/administration/administration.module.ts +++ b/src/frontend/app/administration/administration.module.ts @@ -50,6 +50,7 @@ import { ListAdministrationComponent } from './basket/list/list import { ShippingsAdministrationComponent } from './shipping/shippings-administration.component'; import { ShippingAdministrationComponent } from './shipping/shipping-administration.component'; import { CustomFieldsAdministrationComponent } from './customField/custom-fields-administration.component'; +import { IndexingModelsAdministrationComponent } from './indexingModel/indexing-models-administration.component'; @NgModule({ imports: [ @@ -109,7 +110,8 @@ import { CustomFieldsAdministrationComponent } from './customField/ ShippingsAdministrationComponent, ShippingAdministrationComponent, AccountLinkComponent, - CustomFieldsAdministrationComponent + CustomFieldsAdministrationComponent, + IndexingModelsAdministrationComponent ], entryComponents: [ UsersAdministrationRedirectModalComponent, diff --git a/src/frontend/app/administration/indexingModel/indexing-models-administration.component.html b/src/frontend/app/administration/indexingModel/indexing-models-administration.component.html new file mode 100644 index 00000000000..df16f04f342 --- /dev/null +++ b/src/frontend/app/administration/indexingModel/indexing-models-administration.component.html @@ -0,0 +1,50 @@ +<div class="admin-container" [class.admin-is-mobile]="appService.getViewMode()"> + <mat-sidenav-container autosize class="admin-sidenav-container"> + <mat-sidenav #snav [mode]="appService.getViewMode() ? 'over' : 'side'" + [fixedInViewport]="appService.getViewMode()" fixedTopGap="56" + [opened]="appService.getViewMode() ? false : true"> + <menu-shortcut></menu-shortcut> + <menu-nav></menu-nav> + </mat-sidenav> + <mat-sidenav-content> + <div *ngIf="loading" style="display:flex;height:100%;"> + <mat-spinner style="margin:auto;"></mat-spinner> + </div> + <mat-card *ngIf="!loading" class="card-app-content"> + <div style="display: flex;"> + <div style="flex:1;"> + <mat-form-field class="indexingModelLabel" appearance="outline"> + <mat-label>{{lang.modelName}}</mat-label> + <input matInput name="label" [(ngModel)]="indexingModel.label"> + </mat-form-field> + </div> + <div class="defaultModel"> + <mat-slide-toggle color="primary" name="default" [(ngModel)]="indexingModel.default">{{lang.defaultModel}} + </mat-slide-toggle> + </div> + </div> + <mat-tab-group> + <mat-tab [label]="lang.indexingForm"> + <app-indexing-form #indexingForm></app-indexing-form> + <div class="col-md-12 text-center"> + <button mat-raised-button color="primary" (click)="onSubmit()" + [disabled]="!indexingForm.isModified() && !isModified()">{{lang.update}}</button> + </div> + </mat-tab> + </mat-tab-group> + </mat-card> + </mat-sidenav-content> + <mat-sidenav #snav2 [mode]="appService.getViewMode() ? 'over' : 'side'" + [fixedInViewport]="appService.getViewMode()" fixedTopGap="56" position='end' opened + class="col-md-4 col-sm-12"> + <mat-tab-group> + <mat-tab [label]="lang.availableCustomFields"> + <app-field-list *ngIf="indexingForm !== undefined" [dataCustomFields]="indexingForm.getAvailableCustomFields()"></app-field-list> + </mat-tab> + <mat-tab [label]="lang.availableFields"> + <app-field-list *ngIf="indexingForm !== undefined" [dataFields]="indexingForm.getAvailableFields()"></app-field-list> + </mat-tab> + </mat-tab-group> + </mat-sidenav> + </mat-sidenav-container> +</div> \ No newline at end of file diff --git a/src/frontend/app/administration/indexingModel/indexing-models-administration.component.scss b/src/frontend/app/administration/indexingModel/indexing-models-administration.component.scss new file mode 100644 index 00000000000..f1485c1bb40 --- /dev/null +++ b/src/frontend/app/administration/indexingModel/indexing-models-administration.component.scss @@ -0,0 +1,14 @@ +@import '../../../css/vars.scss'; + +.indexingModelLabel { + ::ng-deep.mat-form-field-wrapper { + padding: 0px; + } +} + +.defaultModel { + display: flex; + align-items: center; + justify-content: center; + padding-left: 10px; +} \ No newline at end of file diff --git a/src/frontend/app/administration/indexingModel/indexing-models-administration.component.ts b/src/frontend/app/administration/indexingModel/indexing-models-administration.component.ts new file mode 100644 index 00000000000..d239c038e22 --- /dev/null +++ b/src/frontend/app/administration/indexingModel/indexing-models-administration.component.ts @@ -0,0 +1,165 @@ +import { Component, OnInit, ViewChild } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { LANG } from '../../translate.component'; +import { NotificationService } from '../../notification.service'; +import { HeaderService } from '../../../service/header.service'; +import { MatDialog } from '@angular/material/dialog'; +import { MatSidenav } from '@angular/material/sidenav'; +import { AppService } from '../../../service/app.service'; +import { tap, catchError, finalize } from 'rxjs/operators'; +import { of } from 'rxjs'; +import { SortPipe } from '../../../plugins/sorting.pipe'; +import { IndexingFormComponent } from '../../indexation/indexing-form/indexing-form.component'; +import { ActivatedRoute } from '@angular/router'; + +declare function $j(selector: any): any; + +@Component({ + templateUrl: "indexing-models-administration.component.html", + styleUrls: [ + 'indexing-models-administration.component.scss', + '../../indexation/indexing-form/indexing-form.component.scss' + ], + providers: [NotificationService, AppService, SortPipe] +}) + +export class IndexingModelsAdministrationComponent implements OnInit { + + @ViewChild('snav', { static: true }) public sidenavLeft: MatSidenav; + @ViewChild('snav2', { static: true }) public sidenavRight: MatSidenav; + + @ViewChild('indexingForm', { static: false }) indexingForm: IndexingFormComponent; + + lang: any = LANG; + + loading: boolean = false; + + indexingModel: any = { + id: 0, + label: '', + default: false, + owner: 0, + private: false + }; + + indexingModelClone: any; + + indexingModelsCustomFields: any[] = []; + + creationMode: boolean = true; + + availableFields: any[] = [ + { + identifier: 'priority', + label: this.lang.priority, + type: 'select', + values: [] + }, + { + identifier: 'confidential', + label: this.lang.confidential, + type: 'radio', + values: ['yes', 'no'] + }, + { + identifier: 'initiator', + label: this.lang.initiator, + type: 'select', + values: [] + }, + { + identifier: 'processLimitDate', + label: this.lang.processLimitDate, + type: 'date', + values: [] + }, + { + identifier: 'arrivalDate', + label: this.lang.arrivalDate, + type: 'date', + values: [] + } + ]; + + availableCustomFields: any[] = [] + + constructor( + public http: HttpClient, + private route: ActivatedRoute, + private notify: NotificationService, + public dialog: MatDialog, + private headerService: HeaderService, + public appService: AppService, + ) { + + } + + ngOnInit(): void { + window['MainHeaderComponent'].setSnav(this.sidenavLeft); + window['MainHeaderComponent'].setSnavRight(this.sidenavRight); + + this.route.params.subscribe((params) => { + if (typeof params['id'] == "undefined") { + this.headerService.setHeader(this.lang.administration); + this.creationMode = true; + this.loading = false; + + } else { + this.creationMode = false; + + this.http.get("../../rest/indexingModels/" + params['id']).pipe( + tap((data: any) => { + this.indexingModel = data.indexingModel; + + this.headerService.setHeader(this.lang.indexingModelModification, this.indexingModel.label); + + this.indexingModelClone = JSON.parse(JSON.stringify(this.indexingModel)); + + }), + finalize(() => this.loading = false), + catchError((err: any) => { + this.notify.handleErrors(err); + return of(false); + }) + ).subscribe(); + } + }); + + + } + + onSubmit() { + this.indexingModel.fields = this.indexingForm.getDatas(); + + this.http.put("../../rest/indexingModels/" + this.indexingModel.id, this.indexingModel).pipe( + tap((data: any) => { + this.indexingForm.setModification(); + this.setModification(); + this.notify.success('sucess!'); + }), + finalize(() => this.loading = false), + catchError((err: any) => { + this.notify.handleErrors(err); + return of(false); + }) + ).subscribe(); + } + + isModified() { + let compare: string = ''; + let compareClone: string = ''; + + compare = JSON.stringify(this.indexingModel); + compareClone = JSON.stringify(this.indexingModelClone); + + if(compare !== compareClone) { + return true; + } else { + return false; + } + } + + setModification() { + this.indexingModelClone = JSON.parse(JSON.stringify(this.indexingModel)); + } +} \ No newline at end of file diff --git a/src/frontend/app/app-common.module.ts b/src/frontend/app/app-common.module.ts index c75bf65044f..2448f33a244 100755 --- a/src/frontend/app/app-common.module.ts +++ b/src/frontend/app/app-common.module.ts @@ -43,6 +43,10 @@ import { SearchHomeComponent } from './search/search-home import { BasketHomeComponent } from './basket/basket-home.component'; +import { IndexingFormComponent } from './indexation/indexing-form/indexing-form.component'; +import { FieldListComponent } from './indexation/field-list/field-list.component'; + + /*MODAL*/ import { AlertComponent } from '../plugins/modal/alert.component'; import { ConfirmComponent } from '../plugins/modal/confirm.component'; @@ -92,7 +96,9 @@ export class MyHammerConfig extends HammerGestureConfig { SmdFabSpeedDialActions, AlertComponent, ConfirmComponent, - PluginAutocomplete + PluginAutocomplete, + IndexingFormComponent, + FieldListComponent ], exports: [ CommonModule, @@ -122,7 +128,9 @@ export class MyHammerConfig extends HammerGestureConfig { SmdFabSpeedDialTrigger, SmdFabSpeedDialActions, DragDropModule, - PluginAutocomplete + PluginAutocomplete, + IndexingFormComponent, + FieldListComponent ], providers: [ LatinisePipe, diff --git a/src/frontend/app/indexation/field-list/field-list.component.html b/src/frontend/app/indexation/field-list/field-list.component.html new file mode 100644 index 00000000000..ffe118b1c3a --- /dev/null +++ b/src/frontend/app/indexation/field-list/field-list.component.html @@ -0,0 +1,131 @@ +<div *ngIf="availableCustomFields !== undefined" class="content" cdkDropList id="customFieldsList" + [cdkDropListConnectedTo]="['indexingModelsCustomFieldsList_mail','indexingModelsCustomFieldsList_contact','indexingModelsCustomFieldsList_process','indexingModelsCustomFieldsList_classement']" + [cdkDropListData]="availableCustomFields" (cdkDropListDropped)="drop($event)"> + <div class="customFieldRow" *ngFor="let field of availableCustomFields" cdkDrag + [cdkDragData]="field"> + <div class="customFieldDrag"> + <i color="primary" class="fas fa-arrows-alt fa-2x" cdkDragHandle></i> + </div> + <div style="flex:1;display: flex;"> + <div class="fieldLabel"> + {{field.label}} + </div> + <div class="fieldInput" [class.advancedInput]="field.type === 'checkbox'"> + <ng-container *ngIf="field.type === 'string'"> + <mat-form-field class="input-form" floatLabel="never"> + <textarea matInput [placeholder]="lang[field.type + 'Input']" matTextareaAutosize + matAutosizeMinRows="1" cdkAutosizeMaxRows="6"></textarea> + </mat-form-field> + </ng-container> + <ng-container *ngIf="field.type === 'select'"> + <mat-form-field class="input-form" floatLabel="never"> + <mat-select [placeholder]="lang[field.type + 'Input']"> + <mat-option *ngFor="let value of field.values" [value]="value"> + {{value}} + </mat-option> + </mat-select> + </mat-form-field> + </ng-container> + <ng-container *ngIf="field.type === 'date'"> + <mat-form-field class="input-form" floatLabel="never"> + <input matInput [matDatepicker]="picker" [placeholder]="lang[field.type + 'Input']" + (click)="picker.open()"> + <mat-datepicker-toggle matSuffix [for]="picker"> + </mat-datepicker-toggle> + <mat-datepicker #picker></mat-datepicker> + </mat-form-field> + </ng-container> + <ng-container *ngIf="field.type === 'radio'"> + <mat-radio-group class="radio-form" color="primary"> + <mat-radio-button *ngFor="let value of field.values" [value]="value"> + {{value}} + </mat-radio-button> + </mat-radio-group> + </ng-container> + <ng-container *ngIf="field.type === 'checkbox'"> + <div class="input-form checkbox-form"> + <mat-selection-list #shoes class="div-list"> + <mat-list-option *ngFor="let value of field.values" [value]="value" + checkboxPosition="before"> + {{value}} + </mat-list-option> + </mat-selection-list> + </div> + <mat-chip-list class="checkbox-selected-list"> + <mat-chip *ngFor="let chip of shoes.selectedOptions.selected" selected> + {{lang.selectedValue}} + </mat-chip> + </mat-chip-list> + </ng-container> + </div> + <div class="fieldState"> + <i class="fas fa-asterisk" [class.noMandatory]="!field.mandatory"></i> + </div> + </div> + </div> +</div> +<div *ngIf="availableFields !== undefined" class="content" cdkDropList id="fieldsList" + [cdkDropListConnectedTo]="['indexingModelsCustomFieldsList_mail','indexingModelsCustomFieldsList_contact','indexingModelsCustomFieldsList_process','indexingModelsCustomFieldsList_classement']" + [cdkDropListData]="availableFields" (cdkDropListDropped)="drop($event)"> + <div class="customFieldRow" *ngFor="let field of availableFields" cdkDrag [cdkDragData]="field"> + <div class="customFieldDrag" cdkDragHandle> + <i color="primary" class="fas fa-arrows-alt fa-2x"></i> + </div> + <div style="flex:1;display: flex;"> + <div class="fieldLabel"> + {{field.label}} + </div> + <div class="fieldInput"> + <ng-container *ngIf="field.type === 'string'"> + <mat-form-field class="input-form" floatLabel="never"> + <textarea matInput [placeholder]="lang[field.type + 'Input']" matTextareaAutosize + matAutosizeMinRows="1" cdkAutosizeMaxRows="6"></textarea> + </mat-form-field> + </ng-container> + <ng-container *ngIf="field.type === 'select'"> + <mat-form-field class="input-form" floatLabel="never"> + <mat-select [placeholder]="lang[field.type + 'Input']"> + <mat-option *ngFor="let value of field.values" [value]="value"> + {{value}} + </mat-option> + </mat-select> + </mat-form-field> + </ng-container> + <ng-container *ngIf="field.type === 'date'"> + <mat-form-field class="input-form" floatLabel="never"> + <input matInput [matDatepicker]="picker" [placeholder]="lang[field.type + 'Input']" + (click)="picker.open()"> + <mat-datepicker-toggle matSuffix [for]="picker"> + </mat-datepicker-toggle> + <mat-datepicker #picker></mat-datepicker> + </mat-form-field> + </ng-container> + <ng-container *ngIf="field.type === 'radio'"> + <mat-radio-group class="radio-form" color="primary"> + <mat-radio-button *ngFor="let value of field.values" [value]="value"> + {{value}} + </mat-radio-button> + </mat-radio-group> + </ng-container> + <ng-container *ngIf="field.type === 'checkbox'"> + <div class="input-form checkbox-form"> + <mat-selection-list #shoes class="div-list"> + <mat-list-option *ngFor="let value of field.values" [value]="value" + checkboxPosition="before"> + {{value}} + </mat-list-option> + </mat-selection-list> + </div> + <mat-chip-list class="checkbox-selected-list"> + <mat-chip *ngFor="let chip of shoes.selectedOptions.selected" selected> + {{lang.selectedValue}} + </mat-chip> + </mat-chip-list> + </ng-container> + </div> + <div class="fieldState"> + <i class="fas fa-asterisk" [class.noMandatory]="!field.mandatory"></i> + </div> + </div> + </div> +</div> \ No newline at end of file diff --git a/src/frontend/app/indexation/field-list/field-list.component.scss b/src/frontend/app/indexation/field-list/field-list.component.scss new file mode 100644 index 00000000000..a51c80f154a --- /dev/null +++ b/src/frontend/app/indexation/field-list/field-list.component.scss @@ -0,0 +1 @@ +@import '../../../css/vars.scss'; diff --git a/src/frontend/app/indexation/field-list/field-list.component.ts b/src/frontend/app/indexation/field-list/field-list.component.ts new file mode 100644 index 00000000000..35cba548c2d --- /dev/null +++ b/src/frontend/app/indexation/field-list/field-list.component.ts @@ -0,0 +1,57 @@ +import { Component, OnInit, Input } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { LANG } from '../../translate.component'; +import { NotificationService } from '../../notification.service'; +import { HeaderService } from '../../../service/header.service'; +import { MatDialog } from '@angular/material/dialog'; +import { AppService } from '../../../service/app.service'; +import { SortPipe } from '../../../plugins/sorting.pipe'; +import { moveItemInArray, CdkDragDrop, transferArrayItem } from '@angular/cdk/drag-drop'; + +declare function $j(selector: any): any; + +@Component({ + selector: 'app-field-list', + templateUrl: "field-list.component.html", + styleUrls: [ + 'field-list.component.scss', + '../../indexation/indexing-form/indexing-form.component.scss' + ], + providers: [NotificationService, AppService, SortPipe] +}) + +export class FieldListComponent implements OnInit { + + lang: any = LANG; + + loading: boolean = false; + + @Input('dataFields') availableCustomFields: any[]; + + @Input('dataCustomFields') availableFields: any[]; + + constructor( + public http: HttpClient, + private notify: NotificationService, + public dialog: MatDialog, + private headerService: HeaderService, + public appService: AppService, + private sortPipe: SortPipe + ) { } + + ngOnInit(): void { } + + drop(event: CdkDragDrop<string[]>) { + event.item.data.unit = event.container.id.split('_')[1]; + + if (event.previousContainer === event.container) { + moveItemInArray(event.container.data, event.previousIndex, event.currentIndex); + } else { + + transferArrayItem(event.previousContainer.data, + event.container.data, + event.previousIndex, + event.currentIndex); + } + } +} \ No newline at end of file diff --git a/src/frontend/app/indexation/indexing-form/indexing-form.component.html b/src/frontend/app/indexation/indexing-form/indexing-form.component.html new file mode 100644 index 00000000000..5900f54314d --- /dev/null +++ b/src/frontend/app/indexation/indexing-form/indexing-form.component.html @@ -0,0 +1,92 @@ +<div class="banner" *ngFor="let category of fieldCategories"> + <div class="title"> + {{lang[category] | uppercase}} + <div class="title-divider"></div> + </div> + <div class="content"> + <div cdkDropList id="indexingModelsCustomFieldsList_{{category}}" + [cdkDropListConnectedTo]="['indexingModelsCustomFieldsList_mail','indexingModelsCustomFieldsList_contact','indexingModelsCustomFieldsList_process','indexingModelsCustomFieldsList_classement','customFieldsList','fieldsList']" + [cdkDropListData]="this['indexingModels_'+category]" (cdkDropListDropped)="drop($event)" + class="indexingModelsCustomFieldsList" style="min-height: 50px;"> + <ng-container *ngFor="let field of this['indexingModels_'+category];let i=index"> + <div class="fieldRow" *ngIf="field.unit === category" cdkDrag cdkDragLockAxis="y" [cdkDragData]="field"> + <div class="fieldLabel"> + <i class="fas fa-bars fa-2x" color="primary" style="cursor: move" cdkDragHandle></i> + <button mat-icon-button [matMenuTriggerFor]="fieldActions" *ngIf="!field.system"> + <mat-icon class="fa fa-ellipsis-v" color="secondary"></mat-icon> + </button> + <mat-menu #fieldActions="matMenu"> + <button mat-menu-item (click)="field.mandatory = !field.mandatory"> + <span *ngIf="!field.mandatory">{{lang.mandatoryField}}</span> + <span *ngIf="field.mandatory">{{lang.optionalField}}</span> + </button> + <mat-divider></mat-divider> + <button mat-menu-item (click)="removeItem('indexingModels_'+category,field,i)"> + <mat-icon class="fa fa-trash" color="warn"></mat-icon> + <span>{{lang.delete}}</span> + </button> + </mat-menu>{{field.label}} + </div> + <div class="fieldInput"> + <ng-container *ngIf="field.type === 'string'"> + <mat-form-field class="input-form" floatLabel="never"> + <textarea matInput [(ngModel)]="field.default_value" + [placeholder]="field.system ? lang[field.type + 'Input'] : lang.defaultValue" + matTextareaAutosize matAutosizeMinRows="1" cdkAutosizeMaxRows="6" + [disabled]="field.system"></textarea> + </mat-form-field> + </ng-container> + <ng-container *ngIf="field.type === 'select'"> + <mat-form-field class="input-form" floatLabel="never"> + <mat-select + [placeholder]="field.system ? lang[field.type + 'Input'] : lang.defaultValue" + [(ngModel)]="field.default_value" [disabled]="field.system"> + <mat-option *ngFor="let value of field.values" [value]="value"> + {{value}} + </mat-option> + </mat-select> + </mat-form-field> + </ng-container> + <ng-container *ngIf="field.type === 'date'"> + <mat-form-field class="input-form" floatLabel="never" (click)="picker.open()"> + <input matInput [matDatepicker]="picker" + [placeholder]="field.system ? lang[field.type + 'Input'] : lang.defaultValue" + [(ngModel)]="field.default_value" [disabled]="field.system"> + <mat-datepicker-toggle matSuffix [for]="picker"> + </mat-datepicker-toggle> + <mat-datepicker #picker></mat-datepicker> + </mat-form-field> + </ng-container> + <ng-container *ngIf="field.type === 'radio'"> + <mat-radio-group class="radio-form" color="primary" [disabled]="field.system" + [(ngModel)]="field.default_value"> + <mat-radio-button *ngFor="let value of field.values" [value]="value"> + {{value}} + </mat-radio-button> + </mat-radio-group> + </ng-container> + <ng-container *ngIf="field.type === 'checkbox'"> + <div class="input-form checkbox-form"> + <mat-selection-list #shoes class="div-list" [disabled]="field.system" + [(ngModel)]="field.default_value"> + <mat-list-option *ngFor="let value of field.values" [value]="value" + checkboxPosition="before"> + {{value}} + </mat-list-option> + </mat-selection-list> + </div> + <mat-chip-list class="checkbox-selected-list" [disabled]="field.system"> + <mat-chip *ngFor="let chip of shoes.selectedOptions.selected" selected> + {{lang.selectedValue}} + </mat-chip> + </mat-chip-list> + </ng-container> + </div> + <div class="fieldState"> + <i class="fas fa-asterisk" [class.noMandatory]="!field.mandatory"></i> + </div> + </div> + </ng-container> + </div> + </div> +</div> \ No newline at end of file diff --git a/src/frontend/app/indexation/indexing-form/indexing-form.component.scss b/src/frontend/app/indexation/indexing-form/indexing-form.component.scss new file mode 100644 index 00000000000..79f4fbf7b6b --- /dev/null +++ b/src/frontend/app/indexation/indexing-form/indexing-form.component.scss @@ -0,0 +1,241 @@ +@import '../../../css/vars.scss'; + +.banner { + display: flex; + flex-direction: column; + margin-top: 30px; + border-radius: 20px; + border: solid 1px #ccc; + position: relative; + padding: 20px; + 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: $primary; + + &-divider { + position: absolute; + width: 99%; + z-index: -1; + top: 17px; + background: white; + height: 1px; + } + } + + .content { + font-size: 16px; + } +} + +.fieldRow { + display: flex; + padding-top: 10px; + padding-bottom: 10px; +} + +.fieldLabel { + color: #4A4A4A; +} + +.fieldState { + padding-left: 10px; + padding-right: 10px; + + i { + font-size: 5px; + color: $primary; + } +} + +.fieldLabel, +.fieldInput { + font-size: 13px; + flex: 1; +} + +.fieldLabel, +.fieldInput, +.fieldState { + align-items: center; + display: flex; + +} + +.input-form { + color: #666; + width: 500px; + + .mat-input-element { + color: $primary; + padding-left: 20px; + } + + input { + padding-left: 20px; + padding-right: 20px; + } + + ::ng-deep.mat-select-value-text { + padding-left: 20px; + color: $primary; + } + + ::ng-deep.mat-form-field-label { + left: 20px; + } + + ::ng-deep.mat-form-field-infix { + padding-bottom: 15px; + } + + ::ng-deep.mat-form-field-flex { + background: white; + border-radius: 30px; + border: solid 1px $primary; + } + + ::ng-deep .mat-form-field-underline { + display: none; + } + + ::ng-deep.mat-form-field-suffix { + margin-right: 20px; + } + + ::ng-deep.mat-select-arrow-wrapper { + padding-right: 20px; + } + + ::ng-deep.mat-list-option { + color: rgba(0, 0, 0, 0.54); + } + + ::ng-deep.mat-form-field-wrapper { + padding: 0px; + } + + textarea.cdk-textarea-autosize-measuring { + padding: 4px 0 !important; + } + + ::ng-deep.mat-datepicker-toggle-default-icon { + height: auto; + } +} + +.mat-form-field-disabled { + ::ng-deep.mat-input-element { + cursor: not-allowed; + padding-left: 20px; + } + + ::ng-deep.mat-form-field-flex { + cursor: not-allowed; + border: dashed 1px rgb(53, 50, 50); + } +} + +.div-list { + + padding: 0px; + max-height: 150px; + overflow: auto; + + .mat-list-item { + font-size: 13px; + } +} + +.checkbox-form { + width: 100%; + padding: 0px; + border: solid 1px $primary; + border-radius: 30px; + overflow: hidden; +} + +.checkbox-selected-list { + margin-top: 10px; + display: flex; + justify-content: center; + + ::ng-deep.mat-chip-list-wrapper { + justify-content: center; + } + + .mat-chip { + font-size: 13px; + } +} + +.radio-form { + display: flex; + width: 100%; + + .mat-radio-button { + flex: 1; + + ::ng-deep.mat-radio-label-content { + font-weight: normal; + color: rgba(0, 0, 0, 0.54); + } + } + +} + +.disabled { + opacity: 0.2; +} + +.customFieldRow { + display: flex; + padding-top: 20px; + padding-bottom: 20px; +} + +.customFieldDrag { + cursor: grab; + width: 50px; + display: flex; + align-items: center; + justify-content: center; +} + +.cdk-drag-animating { + transition: transform 250ms cubic-bezier(0, 0, 0.2, 1); +} + +.cdk-drag-preview { + background: white; + box-sizing: border-box; + border-radius: 4px; + 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); +} + +.indexingModelsCustomFieldsList.cdk-drop-list-dragging .indexingModelsCustomFieldsList:not(.cdk-drag-placeholder) { + transition: transform 250ms cubic-bezier(0, 0, 0.2, 1); +} + +.advancedInput { + flex-direction: column; +} + +.noMandatory { + visibility: hidden; +} \ No newline at end of file diff --git a/src/frontend/app/indexation/indexing-form/indexing-form.component.ts b/src/frontend/app/indexation/indexing-form/indexing-form.component.ts new file mode 100644 index 00000000000..421a8f07bb6 --- /dev/null +++ b/src/frontend/app/indexation/indexing-form/indexing-form.component.ts @@ -0,0 +1,309 @@ +import { Component, OnInit } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { LANG } from '../../translate.component'; +import { NotificationService } from '../../notification.service'; +import { HeaderService } from '../../../service/header.service'; +import { MatDialog } from '@angular/material/dialog'; +import { AppService } from '../../../service/app.service'; +import { tap, catchError, finalize, exhaustMap } from 'rxjs/operators'; +import { of } from 'rxjs'; +import { SortPipe } from '../../../plugins/sorting.pipe'; +import { CdkDragDrop, moveItemInArray, transferArrayItem } from '@angular/cdk/drag-drop'; + +@Component({ + selector: 'app-indexing-form', + templateUrl: "indexing-form.component.html", + styleUrls: ['indexing-form.component.scss'], + providers: [NotificationService, AppService, SortPipe] +}) + +export class IndexingFormComponent implements OnInit { + + lang: any = LANG; + + loading: boolean = false; + + fieldCategories: any[] = ['mail', 'contact', 'process', 'classement']; + + indexingModelsCore: any[] = [ + { + identifier: 'category_id', + label: 'Catégorie', + unit: 'mail', + type: 'select', + system: true, + mandatory: true, + values: [] + }, + { + identifier: 'doctype', + label: 'Type de courrier', + unit: 'mail', + type: 'select', + system: true, + mandatory: true, + values: [] + }, + { + identifier: 'docDate', + label: 'Date du courrier', + unit: 'mail', + type: 'date', + system: true, + mandatory: true, + values: [] + }, + { + identifier: 'arrivalDate', + label: 'Date d\'arrivée', + unit: 'mail', + type: 'date', + system: true, + mandatory: true, + values: [] + }, + { + identifier: 'subject', + label: 'Objet', + unit: 'mail', + type: 'string', + system: true, + mandatory: true, + values: [] + }, + { + identifier: 'contact', + label: 'Expéditeur', + unit: 'contact', + type: 'string', + system: true, + mandatory: true, + values: [] + }, + { + identifier: 'destination', + label: 'Service traitant', + unit: 'process', + type: 'select', + system: true, + mandatory: true, + values: [] + }, + { + identifier: 'folder', + label: 'Dossier', + unit: 'classement', + type: 'string', + system: true, + mandatory: true, + values: [] + } + ]; + + indexingModels_mail: any[] = []; + indexingModels_contact: any[] = []; + indexingModels_process: any[] = []; + indexingModels_classement: any[] = []; + + indexingModels_mailClone: any[] = []; + indexingModels_contactClone: any[] = []; + indexingModels_processClone: any[] = []; + indexingModels_classementClone: any[] = []; + + indexingModelsCustomFields: any[] = []; + + availableFields: any[] = [ + { + identifier: 'priority', + label: this.lang.priority, + type: 'select', + values: [] + }, + { + identifier: 'confidential', + label: this.lang.confidential, + type: 'radio', + values: ['yes', 'no'] + }, + { + identifier: 'initiator', + label: this.lang.initiator, + type: 'select', + values: [] + }, + { + identifier: 'processLimitDate', + label: this.lang.processLimitDate, + type: 'date', + values: [] + }, + { + identifier: 'arrivalDate', + label: this.lang.arrivalDate, + type: 'date', + values: [] + } + ]; + + availableCustomFields: any[] = [] + + constructor( + public http: HttpClient, + private notify: NotificationService, + public dialog: MatDialog, + private headerService: HeaderService, + public appService: AppService, + ) { + + } + + ngOnInit(): void { + + this.fieldCategories.forEach(category => { + this['indexingModels_' + category] = []; + }); + + this.http.get("../../rest/customFields").pipe( + tap((data: any) => { + this.availableCustomFields = data.customFields.map((info: any) => { + info.identifier = 'indexingCustomField_' + info.id; + info.system = false; + return info; + }); + }), + exhaustMap((data) => this.http.get("../../rest/indexingModels/1")), + tap((data: any) => { + let fieldExist: boolean; + if (data.indexingModel.fields.length === 0) { + this.fieldCategories.forEach(element => { + this['indexingModels_' + element] = this.indexingModelsCore.filter((x: any, i: any, a: any) => x.unit === element); + }); + this.notify.error("Champs introuvables! les données de base ont été chargés"); + } else { + data.indexingModel.fields.forEach((field: any) => { + fieldExist = false; + field.label = this.lang[field.identifier]; + field.system = false; + field.values = []; + + let indexFound = this.availableFields.map(avField => avField.identifier).indexOf(field.identifier); + + if (indexFound > -1) { + field.label = this.availableFields[indexFound].label; + field.values = this.availableFields[indexFound].values; + field.type = this.availableFields[indexFound].type; + this.availableFields.splice(indexFound, 1); + fieldExist = true; + } + + indexFound = this.availableCustomFields.map(avField => avField.identifier).indexOf(field.identifier); + + if (indexFound > -1) { + field.label = this.availableCustomFields[indexFound].label; + field.values = this.availableCustomFields[indexFound].values; + field.type = this.availableCustomFields[indexFound].type; + this.availableCustomFields.splice(indexFound, 1); + fieldExist = true; + } + if (this.indexingModelsCore.map(info => info.identifier).indexOf(field.identifier) > -1) { + fieldExist = true; + field.system = true; + } + + if (fieldExist) { + this['indexingModels_' + field.unit].push(field); + } else { + this.notify.error("Le champ "+ field.identifier+" n'existe pas !"); + } + + }); + } + this.fieldCategories.forEach(element => { + this['indexingModels_' + element + 'Clone'] = JSON.parse(JSON.stringify(this['indexingModels_' + element])); + }); + }), + finalize(() => this.loading = false), + catchError((err: any) => { + this.notify.handleErrors(err); + return of(false); + }) + ).subscribe(); + } + + drop(event: CdkDragDrop<string[]>) { + event.item.data.unit = event.container.id.split('_')[1]; + + if (event.previousContainer === event.container) { + moveItemInArray(event.container.data, event.previousIndex, event.currentIndex); + } else { + + transferArrayItem(event.previousContainer.data, + event.container.data, + event.previousIndex, + event.currentIndex); + } + } + + onSubmit() { + let arrIndexingModels: any[] = []; + this.fieldCategories.forEach(category => { + arrIndexingModels = arrIndexingModels.concat(this['indexingModels_' + category]); + }); + } + + removeItem(arrTarget: string, item: any, index: number) { + item.mandatory = false; + if (item.identifier.indexOf('indexingCustomField') > -1) { + this.availableCustomFields.push(item); + this[arrTarget].splice(index, 1); + } else { + this.availableFields.push(item); + this[arrTarget].splice(index, 1); + } + } + + getDatas() { + let arrIndexingModels: any[] = []; + this.fieldCategories.forEach(category => { + arrIndexingModels = arrIndexingModels.concat(this['indexingModels_' + category]); + }); + return arrIndexingModels; + } + + getAvailableFields() { + return this.availableFields; + } + + getAvailableCustomFields() { + return this.availableCustomFields; + } + + isModified() { + let state = false; + let compare: string = ''; + let compareClone: string = ''; + + this.fieldCategories.forEach(category => { + + compare = JSON.stringify((this['indexingModels_' + category])); + compareClone = JSON.stringify((this['indexingModels_' + category + 'Clone'])); + + if (compare !== compareClone) { + state = true; + } + }); + return state; + } + + setModification() { + this.fieldCategories.forEach(element => { + this['indexingModels_' + element + 'Clone'] = JSON.parse(JSON.stringify(this['indexingModels_' + element])); + }); + } + + cancelModification() { + this.fieldCategories.forEach(element => { + this['indexingModels_' + element] = JSON.parse(JSON.stringify(this['indexingModels_' + element + 'Clone'])); + }); + } +} \ No newline at end of file diff --git a/src/frontend/lang/lang-en.ts b/src/frontend/lang/lang-en.ts index d14206164bf..66c28d9e3d3 100755 --- a/src/frontend/lang/lang-en.ts +++ b/src/frontend/lang/lang-en.ts @@ -1101,4 +1101,14 @@ export const LANG_EN = { "privateFolder" : "Private folder", "visibleBy" : "Visible by", "folderInformations" : "Folder Informations", + "process" : "Process", + "modelName" : "Model name", + "defaultModel" : "Default model", + "indexingForm" : "Indexing form", + "availableFields" : "Available field(s)", + "availableCustomFields" : "Available custom field(s)", + "indexingModel" : "Indexing model", + "indexingModelModification" : "Indexing model modification", + "mandatoryField" : "Mandatory field", + "optionalField" : "Optional field", }; diff --git a/src/frontend/lang/lang-fr.ts b/src/frontend/lang/lang-fr.ts index 0628cb7dfda..e430fd3cd83 100755 --- a/src/frontend/lang/lang-fr.ts +++ b/src/frontend/lang/lang-fr.ts @@ -1138,4 +1138,14 @@ export const LANG_FR = { "privateFolder" : "Dossier privé", "visibleBy" : "Visible par", "folderInformations" : "Informations du dossier", + "process" : "Traitement", + "modelName" : "Nom du modèle", + "defaultModel" : "Modèle par défaut", + "indexingForm" : "Formulaire d'enregistrement", + "availableFields" : "Champ(s) disponible(s)", + "availableCustomFields" : "Champ(s) personnalisé(s) disponible(s)", + "indexingModel" : "Modèle d'enregistrement", + "indexingModelModification" : "Modification du modèle d'enregistrement", + "mandatoryField" : "Champ obligatoire", + "optionalField" : "Champ optionnel", }; diff --git a/src/frontend/lang/lang-nl.ts b/src/frontend/lang/lang-nl.ts index d3cf727d3aa..16e39b44ef6 100755 --- a/src/frontend/lang/lang-nl.ts +++ b/src/frontend/lang/lang-nl.ts @@ -1127,4 +1127,14 @@ export const LANG_NL = { "privateFolder" : "Private folder", //_TO_TRANSLATE "visibleBy" : "Visible by", //_TO_TRANSLATE "folderInformations" : "Folder Informations", //_TO_TRANSLATE + "process" : "Process", //_TO_TRANSLATE + "modelName" : "Model name", //_TO_TRANSLATE + "defaultModel" : "Default model", //_TO_TRANSLATE + "indexingForm" : "Indexing form", //_TO_TRANSLATE + "availableFields" : "Available field(s)", //_TO_TRANSLATE + "availableCustomFields" : "Available custom field(s)", //_TO_TRANSLATE + "indexingModel" : "Indexing model", //_TO_TRANSLATE + "indexingModelModification" : "Indexing model modification", //_TO_TRANSLATE + "mandatoryField" : "Mandatory field", //_TO_TRANSLATE + "optionalField" : "Optional field", //_TO_TRANSLATE }; -- GitLab