diff --git a/src/app/shared/icons.module.ts b/src/app/shared/icons.module.ts index 3f0579dd8..f0f7366e9 100644 --- a/src/app/shared/icons.module.ts +++ b/src/app/shared/icons.module.ts @@ -50,6 +50,7 @@ import { faLock, faLockOpen, faMinusCircle, + faMinus, faMoneyBillAlt, faMoneyCheck, faPencilAlt, @@ -142,6 +143,7 @@ library.add( faLocationArrow, faLock, faLockOpen, + faMinus, faMinusCircle, faMoneyBillAlt, faMoneyCheck, diff --git a/src/app/templates/common-resolvers/edit-template.resolver.ts b/src/app/templates/common-resolvers/edit-template.resolver.ts new file mode 100644 index 000000000..ad22d8a50 --- /dev/null +++ b/src/app/templates/common-resolvers/edit-template.resolver.ts @@ -0,0 +1,31 @@ +/** Angular Imports */ +import { Injectable } from '@angular/core'; +import { Resolve, ActivatedRouteSnapshot } from '@angular/router'; + +/** rxjs Imports */ +import { Observable } from 'rxjs'; + +/** Custom Services */ +import { TemplatesService } from '../templates.service'; + +/** + * Edit Template data resolver. + */ +@Injectable() +export class EditTemplateResolver implements Resolve { + + /** + * @param {TemplatesService} templatesService Templates service. + */ + constructor(private templatesService: TemplatesService) {} + + /** + * Returns the template data. + * @returns {Observable} + */ + resolve(route: ActivatedRouteSnapshot): Observable { + const templateId = route.paramMap.get('id'); + return this.templatesService.getEditTemplateData(templateId); + } + +} diff --git a/src/app/templates/template.resolver.ts b/src/app/templates/common-resolvers/template.resolver.ts similarity index 92% rename from src/app/templates/template.resolver.ts rename to src/app/templates/common-resolvers/template.resolver.ts index dcb78dd00..299e41ef0 100644 --- a/src/app/templates/template.resolver.ts +++ b/src/app/templates/common-resolvers/template.resolver.ts @@ -6,7 +6,7 @@ import { Resolve, ActivatedRouteSnapshot } from '@angular/router'; import { Observable } from 'rxjs'; /** Custom Services */ -import { TemplatesService } from './templates.service'; +import { TemplatesService } from '../templates.service'; /** * Template data resolver. diff --git a/src/app/templates/templates.resolver.ts b/src/app/templates/common-resolvers/templates.resolver.ts similarity index 91% rename from src/app/templates/templates.resolver.ts rename to src/app/templates/common-resolvers/templates.resolver.ts index 809306e1c..6da5fb466 100644 --- a/src/app/templates/templates.resolver.ts +++ b/src/app/templates/common-resolvers/templates.resolver.ts @@ -6,7 +6,7 @@ import { Resolve } from '@angular/router'; import { Observable } from 'rxjs'; /** Custom Services */ -import { TemplatesService } from './templates.service'; +import { TemplatesService } from '../templates.service'; /** * Templates data resolver. diff --git a/src/app/templates/edit-template/edit-template.component.html b/src/app/templates/edit-template/edit-template.component.html new file mode 100644 index 000000000..ab1f93838 --- /dev/null +++ b/src/app/templates/edit-template/edit-template.component.html @@ -0,0 +1,143 @@ +
+ + + +
+ + + +
+ + + Entity + + + {{ entity.name }} + + + + + + Type + + + {{ type.name }} + + + + + + Name + + + +
+ +
+ +
+ +
+ + + +
+ + + +
+ + + Mapper Key + + + + + Mapper Value + + + + + + +
+ +
+ +
+ + + +
+ +
+ + + + + + + + Client Parameters + + + +
+ +
+ +
+ + + + + + Loan Parameters + + + +
+ +
+ +
+ + + + + + Repayment Schedule Parameters + + + +
+ +
+ +
+ +
+ +
+ + + + + + +
+ +
diff --git a/src/app/templates/edit-template/edit-template.component.scss b/src/app/templates/edit-template/edit-template.component.scss new file mode 100644 index 000000000..de3be7ac4 --- /dev/null +++ b/src/app/templates/edit-template/edit-template.component.scss @@ -0,0 +1,12 @@ +.container { + + .mapper-button { + max-height: 2.25rem; + margin-top: 1rem; + } + + .parameter { + margin: 1%; + } + +} diff --git a/src/app/templates/edit-template/edit-template.component.spec.ts b/src/app/templates/edit-template/edit-template.component.spec.ts new file mode 100644 index 000000000..f52268284 --- /dev/null +++ b/src/app/templates/edit-template/edit-template.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { EditTemplateComponent } from './edit-template.component'; + +describe('EditTemplateComponent', () => { + let component: EditTemplateComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ EditTemplateComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(EditTemplateComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/templates/edit-template/edit-template.component.ts b/src/app/templates/edit-template/edit-template.component.ts new file mode 100644 index 000000000..9effb4725 --- /dev/null +++ b/src/app/templates/edit-template/edit-template.component.ts @@ -0,0 +1,176 @@ +/** Angular Imports */ +import { Component, OnInit, ViewChild } from '@angular/core'; +import { FormGroup, FormBuilder, Validators, FormControl } from '@angular/forms'; +import { Router, ActivatedRoute } from '@angular/router'; +import { DatePipe } from '@angular/common'; + +/** CKEditor5 Imports */ +import * as ClassicEditor from '@ckeditor/ckeditor5-build-classic'; + +/** Custom Imports */ +import { clientParameterLabels, loanParameterLabels, repaymentParameterLabels } from '../template-parameter-labels'; + +/** Custom Services */ +import { TemplatesService } from '../templates.service'; + +/** + * Edit Template Component. + */ +@Component({ + selector: 'mifosx-edit-template', + templateUrl: './edit-template.component.html', + styleUrls: ['./edit-template.component.scss'] +}) +export class EditTemplateComponent implements OnInit { + + /** CKEditor5 */ + public Editor = ClassicEditor; + /** CKEditor5 Template Reference */ + @ViewChild('ckEditor') ckEditor: any; + + /** Template form. */ + templateForm: FormGroup; + /** Edit Template Data. */ + editTemplateData: any; + /** Template Mappers */ + mappers: any[] = []; + /** Toggles Visibility of Advance Options */ + showAdvanceOptions = false; + + /** Client Parameter Labels */ + clientParameterLabels: string[] = clientParameterLabels; + /** Loan Parameter Labels */ + loanParameterLabels: string[] = loanParameterLabels; + /** Repayment Parameter Labels */ + repaymentParameterLabels: string[] = repaymentParameterLabels; + + /** + * Retrieves the template data from `resolve`. + * @param {FormBuilder} formBuilder Form Builder. + * @param {ActivatedRoute} route Activated Route. + * @param {Router} router Router for navigation. + * @param {DatePipe} datePipe Date Pipe to format date. + */ + constructor(private formBuilder: FormBuilder, + private route: ActivatedRoute, + private router: Router, + private templateService: TemplatesService) { + this.route.data.subscribe((data: { editTemplateData: any }) => { + this.editTemplateData = data.editTemplateData; + this.mappers = this.editTemplateData.template.mappers + .map((mapper: any) => ({ + mappersorder: mapper.mapperorder, + mapperskey: new FormControl(mapper.mapperkey), + mappersvalue: new FormControl(mapper.mappervalue) + })); + }); + } + + ngOnInit() { + this.createTemplateForm(); + this.buildDependencies(); + } + + /** + * Creates the template form. + */ + createTemplateForm() { + this.templateForm = this.formBuilder.group({ + 'entity': [this.editTemplateData.entities.find((entity: any) => entity.name === this.editTemplateData.template.entity).id], + 'type': [this.editTemplateData.types.find((type: any) => type.name === this.editTemplateData.template.type).id], + 'name': [this.editTemplateData.template.name] + }); + } + + /** + * Subscribe to value changes of entity to set default mapper. + */ + buildDependencies() { + const tenantIdentifier = 'default'; // update once global settings are setup. + this.templateForm.get('entity').valueChanges.subscribe((value: any) => { + if (value === 0) { // client + this.mappers.splice(0, 1, { + mappersorder: 0, + mapperskey: new FormControl('client'), + mappersvalue: new FormControl('clients/{{clientId}}?tenantIdentifier=' + tenantIdentifier) + }); + } else { // loan + this.mappers.splice(0, 1, { + mappersorder: 0, + mapperskey: new FormControl('loan'), + mappersvalue: new FormControl('loans/{{loanId}}?associations=all&tenantIdentifier=' + tenantIdentifier ) + }); + } + this.setEditorContent(''); + }); + } + + /** + * Adds a mapper. + */ + addMapper() { + this.mappers.push({ + mappersorder: this.mappers.length, + mapperskey: new FormControl(''), + mappersvalue: new FormControl('') + }); + } + + /** + * Removes a mapper + * @param {any} index Mapper Index + */ + removeMapper(index: any) { + this.mappers.splice(index, 1); + } + + /** + * Adds text to CKEditor at cursor position. + * @param {string} label Template parameter label. + */ + addText(label: string) { + if (this.ckEditor && this.ckEditor.editorInstance) { + this.ckEditor.editorInstance.model.change((writer: any) => { + const insertPosition = this.ckEditor.editorInstance.model.document.selection.getFirstPosition(); + writer.insertText(label, insertPosition); + } ); + } + } + + /** + * Gets the contents of CKEditor. + */ + getEditorContent() { + if (this.ckEditor && this.ckEditor.editorInstance) { + return this.ckEditor.editorInstance.getData(); + } + return ''; + } + + /** + * Sets the contents of CKEditor. + * @param {string} content Editor Content + */ + setEditorContent(content: string) { + if (this.ckEditor && this.ckEditor.editorInstance) { + return this.ckEditor.editorInstance.setData(content); + } + return ''; + } + + submit() { + const template: any = { + ...this.templateForm.value, + mappers: this.mappers.map((mapper: any) => ({ + mappersorder: mapper.mappersorder, + mapperskey: mapper.mapperskey.value, + mappersvalue: mapper.mappersvalue.value + })), + text: this.getEditorContent() + }; + this.templateService.updateTemplate(template, this.editTemplateData.template.id).subscribe(() => { + this.router.navigate(['../'], { relativeTo: this.route }); + }); + } + +} diff --git a/src/app/templates/template-parameter-labels.ts b/src/app/templates/template-parameter-labels.ts new file mode 100644 index 000000000..69e941ff9 --- /dev/null +++ b/src/app/templates/template-parameter-labels.ts @@ -0,0 +1,34 @@ +export const clientParameterLabels = [ + '{{client.accountNo}}', + '{{client.status.value}}', + '{{client.fullname}}', + '{{client.displayName}}', + '{{client.officeName}}', + '{{#client.groups}}', + '{{/client.groups}}' +]; + +export const loanParameterLabels = [ + '{{loan.accountNo}}', + '{{loan.status.value}}', + '{{loan.loanProductId}}', + '{{loan.loanProductName}}', + '{{loan.loanProductDescription}}' +]; + +export const repaymentParameterLabels = [ + '{{loan.repaymentSchedule.loanTermInDays}}', + '{{loan.repaymentSchedule.totalPrincipalDisbursed}}', + '{{loan.repaymentSchedule.totalPrincipalExpected}}', + '{{loan.repaymentSchedule.totalPrincipalPaid}}', + '{{loan.repaymentSchedule.totalInterestCharged}}', + '{{loan.repaymentSchedule.totalFeeChargesCharged}}', + '{{loan.repaymentSchedule.totalPenaltyChargesCharged}}', + '{{loan.repaymentSchedule.totalWaived}}', + '{{loan.repaymentSchedule.totalWrittenOff}}', + '{{loan.repaymentSchedule.totalRepaymentExpected}}', + '{{loan.repaymentSchedule.totalRepayment}}', + '{{loan.repaymentSchedule.totalPaidInAdvance}}', + '{{loan.repaymentSchedule.totalPaidLate}}', + '{{loan.repaymentSchedule.totalOutstanding}}' +]; diff --git a/src/app/templates/templates-routing.module.ts b/src/app/templates/templates-routing.module.ts index 10aaf6f50..b21cd236c 100644 --- a/src/app/templates/templates-routing.module.ts +++ b/src/app/templates/templates-routing.module.ts @@ -11,10 +11,12 @@ import { extract } from '../core/i18n/i18n.service'; /** Custom Components */ import { TemplatesComponent } from './templates.component'; import { ViewTemplateComponent } from './view-template/view-template.component'; +import { EditTemplateComponent } from './edit-template/edit-template.component'; /** Custom Resolvers */ -import { TemplatesResolver } from './templates.resolver'; -import { TemplateResolver } from './template.resolver'; +import { TemplatesResolver } from './common-resolvers/templates.resolver'; +import { TemplateResolver } from './common-resolvers/template.resolver'; +import { EditTemplateResolver } from './common-resolvers/edit-template.resolver'; /** Templates Routes */ const routes: Routes = [ @@ -32,14 +34,22 @@ const routes: Routes = [ }, { path: ':id', - data: { title: extract('View Template'), routeResolveBreadcrumb: ['template', 'name'] }, - resolve: { - template: TemplateResolver - }, + data: { title: extract('View Template'), routeParamBreadcrumb: 'id' }, children: [ { path: '', component: ViewTemplateComponent, + resolve: { + template: TemplateResolver + } + }, + { + path: 'edit', + component: EditTemplateComponent, + data: { title: extract('Edit Template'), breadcrumb: 'Edit', routeParamBreadcrumb: false }, + resolve: { + editTemplateData: EditTemplateResolver + } } ] } @@ -58,7 +68,8 @@ const routes: Routes = [ exports: [RouterModule], providers: [ TemplatesResolver, - TemplateResolver + TemplateResolver, + EditTemplateResolver ] }) export class TemplatesRoutingModule { } diff --git a/src/app/templates/templates.module.ts b/src/app/templates/templates.module.ts index a682248ef..0dc5934f2 100644 --- a/src/app/templates/templates.module.ts +++ b/src/app/templates/templates.module.ts @@ -2,12 +2,14 @@ import { NgModule } from '@angular/core'; /** Custom Modules */ +import { CKEditorModule } from '@ckeditor/ckeditor5-angular'; import { SharedModule } from '../shared/shared.module'; import { TemplatesRoutingModule } from './templates-routing.module'; /** Custom Components */ import { TemplatesComponent } from './templates.component'; import { ViewTemplateComponent } from './view-template/view-template.component'; +import { EditTemplateComponent } from './edit-template/edit-template.component'; /** * Templates Module @@ -16,12 +18,14 @@ import { ViewTemplateComponent } from './view-template/view-template.component'; */ @NgModule({ imports: [ + CKEditorModule, SharedModule, TemplatesRoutingModule ], declarations: [ TemplatesComponent, ViewTemplateComponent, + EditTemplateComponent, ] }) export class TemplatesModule { } diff --git a/src/app/templates/templates.service.ts b/src/app/templates/templates.service.ts index 2b5795d1e..0640ce7e5 100644 --- a/src/app/templates/templates.service.ts +++ b/src/app/templates/templates.service.ts @@ -33,6 +33,23 @@ export class TemplatesService { return this.http.get(`/templates/${templateId}`); } + /** + * @param templateId Template Id. + * @returns {Observable}. + */ + getEditTemplateData(templateId: string): Observable { + return this.http.get(`/templates/${templateId}/template`); + } + + /** + * @param templateData Templete Data to be edited. + * @param templateId Template Id. + * @returns {Observable}. + */ + updateTemplate(templateData: any, templateId: any): Observable { + return this.http.put(`/templates/${templateId}`, templateData); + } + /** * @param templateId Template ID. * @returns {Observable} diff --git a/src/app/templates/view-template/view-template.component.html b/src/app/templates/view-template/view-template.component.html index e9b405f2c..7790d2f16 100644 --- a/src/app/templates/view-template/view-template.component.html +++ b/src/app/templates/view-template/view-template.component.html @@ -1,6 +1,6 @@
- @@ -20,6 +20,14 @@
+
+ Name +
+ +
+ {{ templateData.name }} +
+
Entity
diff --git a/src/typings.d.ts b/src/typings.d.ts index 904adb4e4..3e661d182 100644 --- a/src/typings.d.ts +++ b/src/typings.d.ts @@ -12,3 +12,9 @@ interface NodeModule { } declare module 'chart.js'; + +declare module '@ckeditor/ckeditor5-build-classic' { + const ClassicEditorBuild: any; + + export = ClassicEditorBuild; +}