feat: add create and edit sms-campaign pages (#851)

Fixes: #167
Fixes: #764
This commit is contained in:
Karan Takalkar
2020-07-30 12:14:49 +05:30
committed by GitHub
parent 571f71192c
commit 3b686863f8
39 changed files with 1966 additions and 112 deletions
@@ -53,6 +53,8 @@ import { EditOfficeComponent } from './offices/edit-office/edit-office.component
import { BulkImportComponent } from './bulk-import/bulk-import.component';
import { ViewBulkImportComponent } from './bulk-import/view-bulk-import/view-bulk-import.component';
import { ViewLoanProvisioningCriteriaComponent } from './loan-provisioning-criteria/view-loan-provisioning-criteria/view-loan-provisioning-criteria.component';
import { CreateCampaignComponent } from './sms-campaigns/create-campaign/create-campaign.component';
import { EditCampaignComponent } from './sms-campaigns/edit-campaign/edit-campaign.component';
/** Custom Resolvers */
import { LoanProvisioningCriteriaResolver } from './loan-provisioning-criteria/loan-provisioning-criteria.resolver';
@@ -87,6 +89,7 @@ import { EditCashierResolver } from './tellers/common-resolvers/edit-cashier.res
import { HolidayTemplateResolver } from './holidays/holiday-template.resolver';
import { AdhocQueryAndTemplateResolver } from './adhoc-query/common-resolvers/adhoc-query-and-template.resolver';
import { BulkImportResolver } from './bulk-import/bulk-import.resolver';
import { SmsCampaignTemplateResolver } from './sms-campaigns/common-resolvers/sms-campaign-template.resolver';
/** Organization Routes */
const routes: Routes = [
@@ -261,13 +264,35 @@ const routes: Routes = [
smsCampaigns: SmsCampaignsResolver
}
},
{
path: 'create',
data: { title: extract('Create SMS Campaign'), breadcrumb: 'Create Campaign' },
component: CreateCampaignComponent,
resolve: {
smsCampaignTemplate: SmsCampaignTemplateResolver
}
},
{
path: ':id',
component: ViewCampaignComponent,
data: { title: extract('View SMS Campaign'), routeResolveBreadcrumb: ['smsCampaign', 'campaignName'] },
resolve: {
smsCampaign: SmsCampaignResolver
}
},
runGuardsAndResolvers: 'always',
children: [
{
path: '',
component: ViewCampaignComponent,
},
{
path: 'edit',
component: EditCampaignComponent,
data: { title: extract('Edit SMS Campaign'), breadcrumb: 'Edit', routeResolveBreadcrumb: false},
resolve: {
smsCampaignTemplate: SmsCampaignTemplateResolver,
}
}
]
}
]
},
@@ -564,6 +589,7 @@ const routes: Routes = [
CurrenciesResolver,
SmsCampaignsResolver,
SmsCampaignResolver,
SmsCampaignTemplateResolver,
AdhocQueriesResolver,
AdhocQueryResolver,
TellersResolver,
+17 -1
View File
@@ -54,6 +54,14 @@ import { EditHolidayComponent } from './holidays/edit-holiday/edit-holiday.compo
import { EditAdhocQueryComponent } from './adhoc-query/edit-adhoc-query/edit-adhoc-query.component';
import { BulkImportComponent } from './/bulk-import/bulk-import.component';
import { ViewBulkImportComponent } from './bulk-import/view-bulk-import/view-bulk-import.component';
import { CreateCampaignComponent } from './sms-campaigns/create-campaign/create-campaign.component';
import { SmsCampaignStepComponent } from './sms-campaigns/sms-campaign-stepper/sms-campaign-step/sms-campaign-step.component';
import { CampaignMessageStepComponent } from './sms-campaigns/sms-campaign-stepper/campaign-message-step/campaign-message-step.component';
import { CampaignPreviewStepComponent } from './sms-campaigns/sms-campaign-stepper/campaign-preview-step/campaign-preview-step.component';
import { BusinessRuleParametersComponent } from './sms-campaigns/sms-campaign-stepper/sms-campaign-step/business-rule-parameters/business-rule-parameters.component';
import { EditCampaignComponent } from './sms-campaigns/edit-campaign/edit-campaign.component';
import { EditSmsCampaignStepComponent } from './sms-campaigns/sms-campaign-stepper/edit-sms-campaign-step/edit-sms-campaign-step.component';
import { EditBusinessRuleParametersComponent } from './sms-campaigns/sms-campaign-stepper/edit-sms-campaign-step/edit-business-rule-parameters/edit-business-rule-parameters.component';
/**
* Organization Module
@@ -113,7 +121,15 @@ import { ViewBulkImportComponent } from './bulk-import/view-bulk-import/view-bul
EditHolidayComponent,
EditAdhocQueryComponent,
BulkImportComponent,
ViewBulkImportComponent
ViewBulkImportComponent,
CreateCampaignComponent,
SmsCampaignStepComponent,
CampaignMessageStepComponent,
CampaignPreviewStepComponent,
BusinessRuleParametersComponent,
EditCampaignComponent,
EditSmsCampaignStepComponent,
EditBusinessRuleParametersComponent
]
})
export class OrganizationModule { }
+25 -2
View File
@@ -185,8 +185,31 @@ export class OrganizationService {
* @param {string} smsCampaignId SMS Campaign ID of SMS Campaign.
* @returns {Observable<any>} SMS Campaign.
*/
getSmsCampaign(smsCampaignId: string): Observable<any> {
return this.http.get(`/smscampaigns/${smsCampaignId}`);
getSmsCampaign(campaignId: string): Observable<any> {
return this.http.get(`/smscampaigns/${campaignId}`);
}
/**
* @param {any} campaign Campaign to be created.
* @returns {Observable<any>}
*/
createSmsCampaign(campaign: any): Observable<any> {
return this.http.post('/smscampaigns', campaign);
}
/**
* @param {any} campaign Campaign to be updated.
* @returns {Observable<any>}
*/
updateSmsCampaign(campaign: any, campaignId: string): Observable<any> {
return this.http.put(`/smscampaigns/${campaignId}`, campaign);
}
/**
* @returns {Observable<any>} SMS Campaign template
*/
getSmsCampaignTemplate(): Observable<any> {
return this.http.get('/smscampaigns/template');
}
/**
@@ -0,0 +1,30 @@
/** Angular Imports */
import { Injectable } from '@angular/core';
import { Resolve } from '@angular/router';
/** rxjs Imports */
import { Observable } from 'rxjs';
/** Custom Services */
import { OrganizationService } from '../../organization.service';
/**
* SMS Campaign Template resolver.
*/
@Injectable()
export class SmsCampaignTemplateResolver implements Resolve<Object> {
/**
* @param {OrganizationService} organizationService Organization service.
*/
constructor(private organizationService: OrganizationService) {}
/**
* Returns the SMS Campaign Template.
* @returns {Observable<any>}
*/
resolve(): Observable<any> {
return this.organizationService.getSmsCampaignTemplate();
}
}
@@ -0,0 +1,63 @@
<div class="container">
<mat-horizontal-stepper class="mat-elevation-z8" labelPosition="bottom" #smsCampaignStepper>
<ng-template matStepperIcon="number">
<fa-icon icon="pencil-alt" size="sm"></fa-icon>
</ng-template>
<ng-template matStepperIcon="edit">
<fa-icon icon="pencil-alt" size="sm"></fa-icon>
</ng-template>
<ng-template matStepperIcon="done">
<fa-icon icon="check" size="sm"></fa-icon>
</ng-template>
<ng-template matStepperIcon="error">
<fa-icon icon="exclamation-triangle" size="lg"></fa-icon>
</ng-template>
<ng-template matStepperIcon="preview">
<fa-icon icon="eye" size="sm"></fa-icon>
</ng-template>
<mat-step [stepControl]="smsCampaignForm">
<ng-template matStepLabel>CAMPAIGN</ng-template>
<mifosx-sms-campaign-step
[smsCampaignTemplate]="smsCampaignTemplate"
(templateParameters)="setParameters($event)"
>
</mifosx-sms-campaign-step>
</mat-step>
<mat-step>
<ng-template matStepLabel>MESSAGE</ng-template>
<mifosx-campaign-message-step
[templateParameters]="templateParameters"
[smsCampaignFormValid]="smsCampaignForm.valid"
>
</mifosx-campaign-message-step>
</mat-step>
<mat-step state="preview" *ngIf="smsCampaignForm.valid" completed>
<ng-template matStepLabel>PREVIEW</ng-template>
<mifosx-campaign-preview-step
[smsCampaignTemplate]="smsCampaignTemplate"
[campaign]="smsCampaign" (submit)="submit()"
>
</mifosx-campaign-preview-step>
</mat-step>
</mat-horizontal-stepper>
</div>
@@ -0,0 +1,3 @@
.container {
width: 60%;;
}
@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { CreateCampaignComponent } from './create-campaign.component';
describe('CreateCampaignComponent', () => {
let component: CreateCampaignComponent;
let fixture: ComponentFixture<CreateCampaignComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ CreateCampaignComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(CreateCampaignComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,99 @@
/** Angular Imports */
import { Component, ViewChild } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { DatePipe } from '@angular/common';
/** Custom Components */
import { SmsCampaignStepComponent } from '../sms-campaign-stepper/sms-campaign-step/sms-campaign-step.component';
import { CampaignMessageStepComponent } from '../sms-campaign-stepper/campaign-message-step/campaign-message-step.component';
/** Custom Services */
import { OrganizationService } from 'app/organization/organization.service';
/**
* Create SMS Campaign Component
*/
@Component({
selector: 'mifosx-create-campaign',
templateUrl: './create-campaign.component.html',
styleUrls: ['./create-campaign.component.scss']
})
export class CreateCampaignComponent {
/** SMS Campaign Template */
smsCampaignTemplate: any;
/** Run report headers */
templateParameters: any;
/** SMS Campaign Step */
@ViewChild(SmsCampaignStepComponent) smsCampaignStep: SmsCampaignStepComponent;
/** Campaign Message Step */
@ViewChild(CampaignMessageStepComponent) campaignMessageStep: CampaignMessageStepComponent;
/**
* Fetches campaign template from `resolve`
* @param {ActivatedRoute} route Activated Route
* @param {Router} router Router
* @param {OrganizationService} organizationService Organization Service
* @param {DatePipe} datePipe Date Pipe
*/
constructor(private route: ActivatedRoute,
private router: Router,
private organizationService: OrganizationService,
private datePipe: DatePipe) {
this.route.data.subscribe((data: { smsCampaignTemplate: any }) => {
this.smsCampaignTemplate = data.smsCampaignTemplate;
});
}
/**
* Retrieves SMS Campaign forms in a single form group to check validity.
*/
get smsCampaignForm() {
return this.smsCampaignStep.smsCampaignFormGroup;
}
/**
* Retrieves SMS Campaign object after formatting form values.
*/
get smsCampaign() {
return {
...this.smsCampaignStep.smsCampaignFormGroupValue,
...this.campaignMessageStep.campaignMessage
};
}
/**
* Passes template parameters to campaign-message-step.
* @param {any} $event Template parameters
*/
setParameters($event: any) {
this.templateParameters = $event;
}
/**
* Creates SMS Campaign.
*/
submit() {
// TODO: Update once language and date settings are setup
const locale = 'en';
const dateFormat = 'dd MMMM yyyy';
const dateTimeFormat = 'dd MMMM yyyy HH:mm:ss';
const smsCampaign = {
...this.smsCampaign,
campaignType: this.smsCampaign.isNotification ? 2 : 1,
submittedOnDate: this.datePipe.transform(new Date(), dateFormat),
dateTimeFormat,
dateFormat,
locale
};
if (this.smsCampaign.triggerType === 2) {
const prevRecurrenceDate: Date = smsCampaign.recurrenceStartDate;
smsCampaign.recurrenceStartDate = this.datePipe.transform(prevRecurrenceDate, dateTimeFormat);
}
this.organizationService.createSmsCampaign(smsCampaign).subscribe((response: any) => {
this.router.navigate(['../', response.resourceId], { relativeTo: this.route });
});
}
}
@@ -0,0 +1,65 @@
<div class="container">
<mat-horizontal-stepper class="mat-elevation-z8" labelPosition="bottom" #smsCampaignStepper>
<ng-template matStepperIcon="number">
<fa-icon icon="pencil-alt" size="sm"></fa-icon>
</ng-template>
<ng-template matStepperIcon="edit">
<fa-icon icon="pencil-alt" size="sm"></fa-icon>
</ng-template>
<ng-template matStepperIcon="done">
<fa-icon icon="check" size="sm"></fa-icon>
</ng-template>
<ng-template matStepperIcon="error">
<fa-icon icon="exclamation-triangle" size="lg"></fa-icon>
</ng-template>
<ng-template matStepperIcon="preview">
<fa-icon icon="eye" size="sm"></fa-icon>
</ng-template>
<mat-step completed>
<ng-template matStepLabel>CAMPAIGN</ng-template>
<mifosx-edit-sms-campaign-step
[smsCampaign]="smsCampaign"
[smsCampaignTemplate]="smsCampaignTemplate"
(templateParameters)="setParameters($event)"
>
</mifosx-edit-sms-campaign-step>
</mat-step>
<mat-step>
<ng-template matStepLabel>MESSAGE</ng-template>
<mifosx-campaign-message-step
[templateParameters]="templateParameters"
[editCampaignMessage]="smsCampaign.campaignMessage"
>
</mifosx-campaign-message-step>
</mat-step>
<mat-step state="preview" completed>
<ng-template matStepLabel>PREVIEW</ng-template>
<mifosx-campaign-preview-step
[campaign]="smsCampaign"
[editedCampaignMessage]="campaignMessage"
[smsCampaignTemplate]="smsCampaignTemplate" (submit)="submit()"
>
</mifosx-campaign-preview-step>
</mat-step>
</mat-horizontal-stepper>
</div>
@@ -0,0 +1,3 @@
.container {
width: 60%;;
}
@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { EditCampaignComponent } from './edit-campaign.component';
describe('EditCampaignComponent', () => {
let component: EditCampaignComponent;
let fixture: ComponentFixture<EditCampaignComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ EditCampaignComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(EditCampaignComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,94 @@
/** Angular Imports */
import { Component, ViewChild } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { DatePipe } from '@angular/common';
/** Custom Services */
import { OrganizationService } from 'app/organization/organization.service';
/** Custom Components */
import { CampaignMessageStepComponent } from '../sms-campaign-stepper/campaign-message-step/campaign-message-step.component';
/**
* Edit Campaign Component
*/
@Component({
selector: 'mifosx-edit-campaign',
templateUrl: './edit-campaign.component.html',
styleUrls: ['./edit-campaign.component.scss']
})
export class EditCampaignComponent {
/** smsCampaign */
smsCampaign: any;
/** SMS Campaign Template */
smsCampaignTemplate: any;
/** Run report headers */
templateParameters: any;
/** Campaign Message Step */
@ViewChild(CampaignMessageStepComponent) campaignMessageStep: CampaignMessageStepComponent;
/**
* Fetches campaign template from `resolve`
* @param {ActivatedRoute} route Activated Route
* @param {Router} router Router
* @param {DatePipe} datePipe Date Pipe
* @param {OrganizationService} organizationService Organiztion Service
*/
constructor(private route: ActivatedRoute,
private router: Router,
private datePipe: DatePipe,
private organizationService: OrganizationService) {
this.route.data.subscribe((data: { smsCampaign: any, smsCampaignTemplate: any }) => {
this.smsCampaignTemplate = data.smsCampaignTemplate;
this.smsCampaign = data.smsCampaign;
this.smsCampaign.editFlag = true;
});
}
/**
* Retrieves edited campaign message.
*/
get campaignMessage() {
return this.campaignMessageStep.campaignMessage.message;
}
/**
* Passes template parameters to campaign-message-step.
* @param {any} $event Template parameters
*/
setParameters($event: any) {
this.templateParameters = $event;
}
/**
* Updates SMS Campaign.
*/
submit() {
// TODO: Update once language and date settings are setup
const locale = 'en';
const dateFormat = 'dd MMMM yyyy';
const dateTimeFormat = 'dd MMMM yyyy HH:mm:ss';
const smsCampaign: any = {
campaignName: this.smsCampaign.campaignName,
campaignType: this.smsCampaign.isNotification ? 2 : 1,
isNotification: this.smsCampaign.isNotification,
triggerType: this.smsCampaign.triggerType.id,
providerId: this.smsCampaign.providerId === 0 ? null : this.smsCampaign.providerId,
runReportId: this.smsCampaign.runReportId,
message: this.campaignMessage,
paramValue: JSON.parse(this.smsCampaign.paramValue),
dateTimeFormat,
dateFormat,
locale
};
if (this.smsCampaign.triggerType.id === 2) {
smsCampaign.recurrenceStartDate = this.datePipe.transform(new Date(this.smsCampaign.recurrenceStartDate), dateTimeFormat);
}
this.organizationService.updateSmsCampaign(smsCampaign, this.smsCampaign.id).subscribe((response: any) => {
this.router.navigate(['../'], { relativeTo: this.route });
});
}
}
@@ -0,0 +1,28 @@
<div fxLayout="column">
<mat-form-field fxFlex="98%">
<mat-label>Campaign Message</mat-label>
<textarea matInput [formControl]="message"></textarea>
</mat-form-field>
<h3 fxFlex="98%" class="mat-h3">Template Parameters</h3>
<div fxLayout="row wrap" fxLayout.xs="column" fxLayoutAlign="center">
<button mat-stroked-button color="primary" class="parameter"
*ngFor="let label of parameterLabels" (click)="addText(label)">
{{ label }}
</button>
</div>
<div fxLayout="row" class="margin-t" fxLayout.xs="column" fxLayoutAlign="center" fxLayoutGap="2%">
<button mat-raised-button matStepperPrevious>
<fa-icon icon="arrow-left"></fa-icon>&nbsp;&nbsp;
Previous
</button>
<button mat-raised-button matStepperNext [disabled]="!smsCampaignFormValid">
Next&nbsp;&nbsp;
<fa-icon icon="arrow-right"></fa-icon>
</button>
</div>
</div>
@@ -0,0 +1,11 @@
h3 {
font-weight: 500;
}
.margin-t {
margin-top: 1.75em;
}
.parameter {
margin: 1%;
}
@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { CampaignMessageStepComponent } from './campaign-message-step.component';
describe('CampaignMessageStepComponent', () => {
let component: CampaignMessageStepComponent;
let fixture: ComponentFixture<CampaignMessageStepComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ CampaignMessageStepComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(CampaignMessageStepComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,62 @@
/** Angular Imports */
import { Component, Input, OnChanges } from '@angular/core';
import { FormControl } from '@angular/forms';
/**
* Campaign Message Step
*/
@Component({
selector: 'mifosx-campaign-message-step',
templateUrl: './campaign-message-step.component.html',
styleUrls: ['./campaign-message-step.component.scss']
})
export class CampaignMessageStepComponent implements OnChanges {
/** Column headers */
@Input() templateParameters: any[];
/** Valdity check for sms campaign form */
@Input() smsCampaignFormValid: boolean;
/** [Optional] SMS Campaign message for edit form */
@Input() editCampaignMessage: any;
/** Camapaign Message */
message = new FormControl('');
/** Column header names */
parameterLabels: any[];
constructor() { }
/**
* Sets template parameters once response headers are retrieved.
*/
ngOnChanges() {
this.message.patchValue('');
this.parameterLabels = [];
if (this.templateParameters) {
this.parameterLabels = this.templateParameters.map((entry: any) => {
return entry.columnName;
});
}
if (this.editCampaignMessage) {
this.message.patchValue(this.editCampaignMessage);
}
}
/**
* SMS Campaign message.
*/
get campaignMessage() {
return { message: this.message.value };
}
/**
* Adds template parameter interpolation to campaign message.
* @param {string} label Template parameter label.
*/
addText(label: string) {
const prevText = this.message.value;
const newText = prevText + ` {{${label}}} `;
this.message.patchValue(newText);
}
}
@@ -0,0 +1,37 @@
<div class="tab-content mat-typography">
<mat-list>
<mat-list-item>
Campaign Name : {{ campaign.campaignName }}
</mat-list-item>
<mat-list-item>
SMS Provider : {{ (campaign.providerId | find:smsProviders:'id':'name') || "Unassigned" }}
</mat-list-item>
<mat-list-item>
Trigger Type : {{ (campaign.editFlag ? campaign.triggerType.id : campaign.triggerType) | find:triggerTypes:'id':'value' }}
</mat-list-item>
<mat-list-item>
Bussiness Rule : {{ campaign.editFlag ? campaign.reportName : campaign.paramValue?.reportName }}
</mat-list-item>
<div fxLayout="column" fxLayoutGap="10px" class="template-message">
<h3>Campaign Message :</h3>
<textarea matInput disabled>{{ campaign.editFlag ? editedCampaignMessage : campaign.message }}</textarea>
</div>
</mat-list>
<div fxLayout="row" class="margin-t" fxLayout.xs="column" fxLayoutAlign="center" fxLayoutGap="2%">
<button mat-raised-button [routerLink]="['../']">
Cancel
</button>
<button mat-raised-button color="primary" (click)="submit.emit()">
Submit
</button>
</div>
</div>
@@ -0,0 +1,11 @@
.tab-content {
padding: 1%;
margin: 1%;
.template-message {
padding-inline: 1.5%;
margin-top: 1%;
}
.margin-t {
margin-top: 1em;
}
}
@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { CampaignPreviewStepComponent } from './campaign-preview-step.component';
describe('CampaignPreviewStepComponent', () => {
let component: CampaignPreviewStepComponent;
let fixture: ComponentFixture<CampaignPreviewStepComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ CampaignPreviewStepComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(CampaignPreviewStepComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,39 @@
/** Angular Imports */
import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core';
/**
* Campaign Preview Step.
*/
@Component({
selector: 'mifosx-campaign-preview-step',
templateUrl: './campaign-preview-step.component.html',
styleUrls: ['./campaign-preview-step.component.scss']
})
export class CampaignPreviewStepComponent implements OnInit {
/** SMS Campaign */
@Input() campaign: any;
/** [Optional] SMS Campaign Template for create form */
@Input() smsCampaignTemplate: any;
/** [Optional] SMS Campaign Message for edit form */
@Input() editedCampaignMessage: any;
/** Trigger types options */
triggerTypes: any[];
/** SMS providers options */
smsProviders: any[];
/** Emits submit() event */
@Output() submit = new EventEmitter;
constructor() { }
/**
* Sets SMS providers and trigger types options.
*/
ngOnInit() {
this.triggerTypes = this.smsCampaignTemplate.triggerTypeOptions;
this.smsProviders = this.smsCampaignTemplate.smsProviderOptions;
}
}
@@ -0,0 +1,65 @@
<div fxLayout="column">
<mat-divider fxFlex="98%"></mat-divider>
<h3 fxFlex="98%" class="mat-h3">Business Rule Parameters</h3>
<form [formGroup]="ReportForm">
<div fxLayout="row wrap" fxLayoutGap="2%">
<ng-container *ngFor="let param of paramData" [ngSwitch]="param.displayType">
<ng-container *ngIf="ReportForm.controls[param.name]">
<mat-form-field fxFlex="48%" *ngSwitchCase="'text'">
<mat-label>{{param.label}}</mat-label>
<input matInput required [formControlName]="param.name">
<mat-error *ngIf="ReportForm.controls[param.name].hasError('required')">
{{param.label}} is <strong>required</strong>
</mat-error>
</mat-form-field>
<mat-form-field fxFlex="48%" *ngSwitchCase="'date'">
<mat-label>{{param.label}}</mat-label>
<input matInput [min]="minDate" [max]="maxDate" [matDatepicker]="runReportDatePicker" required
[formControlName]="param.name">
<mat-datepicker-toggle matSuffix [for]="runReportDatePicker"></mat-datepicker-toggle>
<mat-datepicker #runReportDatePicker></mat-datepicker>
<mat-error *ngIf="ReportForm.controls[param.name].hasError('required')">
{{param.label}} is <strong>required</strong>
</mat-error>
</mat-form-field>
<mat-form-field fxFlex="48%" *ngSwitchCase="'select'">
<mat-label>{{param.label}}</mat-label>
<mat-select required [formControlName]="param.name" [compareWith]="compareOptions">
<mat-option *ngFor="let option of param.selectOptions" [value]="option">
{{option.name}}
</mat-option>
</mat-select>
<mat-error *ngIf="ReportForm.controls[param.name].hasError('required')">
{{param.label}} is <strong>required</strong>
</mat-error>
</mat-form-field>
</ng-container>
</ng-container>
</div>
</form>
</div>
<div fxLayout="row" class="margin-t" fxLayout.xs="column" fxLayoutAlign="center" fxLayoutGap="2%">
<button mat-raised-button matStepperPrevious disabled>
<fa-icon icon="arrow-left"></fa-icon>&nbsp;&nbsp;
Previous
</button>
<button mat-raised-button matStepperNext>
Next&nbsp;&nbsp;
<fa-icon icon="arrow-right"></fa-icon>
</button>
</div>
@@ -0,0 +1,15 @@
h3 {
font-weight: 500;
}
mat-divider {
margin: 1em 0 2em 0;
}
.margin-t {
margin-top: 1em;
}
.parameter {
margin-inline: 1%;
}
@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { EditBusinessRuleParametersComponent } from './edit-business-rule-parameters.component';
describe('EditBusinessRuleParametersComponent', () => {
let component: EditBusinessRuleParametersComponent;
let fixture: ComponentFixture<EditBusinessRuleParametersComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ EditBusinessRuleParametersComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(EditBusinessRuleParametersComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,196 @@
/** Angular Imports */
import { Component, OnChanges, Input, Output, EventEmitter } from '@angular/core';
import { Validators, FormGroup, FormControl } from '@angular/forms';
import { DatePipe } from '@angular/common';
/** Rxjs Imports */
import { distinctUntilChanged } from 'rxjs/operators';
/** Custom Services */
import { ReportsService } from 'app/reports/reports.service';
/** Custom Models */
import { ReportParameter } from 'app/reports/common-models/report-parameter.model';
import { SelectOption } from 'app/reports/common-models/select-option.model';
/**
* Edit Business Rule Parameters.
*/
@Component({
selector: 'mifosx-edit-business-rule-parameters',
templateUrl: './edit-business-rule-parameters.component.html',
styleUrls: ['./edit-business-rule-parameters.component.scss']
})
export class EditBusinessRuleParametersComponent implements OnChanges {
/** Run Report Parameters Data */
@Input() paramData: any;
/** SMS Campaign */
@Input() smsCampaign: any;
/** Template Parameters Event Emitter */
@Output() templateParameters = new EventEmitter();
/** Initializes new form group ReportForm */
ReportForm = new FormGroup({});
/** Array of all parent parameters */
parentParameters: any[] = [];
/** Displayed user choices */
paramValue: any;
/**
* @param {ReportsService} reportsService Reports Service
*/
constructor(private reportsService: ReportsService,
private datePipe: DatePipe) { }
ngOnChanges() {
if (this.paramData) {
this.ReportForm = new FormGroup({});
this.paramValue = JSON.parse(this.smsCampaign.paramValue);
this.createRunReportForm();
this.disableFormWhenValid();
this.getResponseHeaders();
}
}
/**
* Establishes form controls for Report Parameter's name attribute,
* Fetches dropdown options and builds child dependencies.
*/
createRunReportForm() {
this.paramData.forEach(
(param: any) => {
if (!param.parentParameterName) { // Non Child Parameter
this.ReportForm.addControl(param.name, new FormControl('', Validators.required));
const controlValue = this.paramValue[param.variable].toString();
switch (param.displayType) {
case 'text':
this.ReportForm.get(param.name).patchValue(controlValue);
break;
case 'select':
this.fetchSelectOptions(param, param.name);
break;
case 'date':
const dateFormat = 'yyyy-MM-dd';
const newControlValue = this.datePipe.transform(controlValue, dateFormat);
this.ReportForm.get(param.name).patchValue(newControlValue);
break;
}
} else { // Child Parameter
const parent: ReportParameter = this.paramData
.find((entry: any) => entry.name === param.parentParameterName);
// TODO: Refactor once Report Parameter Model is updated for multi-child support.
if (parent.childParameter instanceof Array) {
parent.childParameter.push(param);
} else {
parent.childParameter = [];
parent.childParameter.push(param);
}
const parentNames = this.parentParameters.map(parameter => parameter.name);
if (!parentNames.includes(parent.name)) {
this.parentParameters.push(parent);
} else {
const index = parentNames.indexOf(parent.name);
this.parentParameters[index] = parent;
}
}
});
this.setChildControls();
}
/**
* Subscribes to changes in parent parameters value, builds child parameter vis-a-vis parent's value.
*/
setChildControls() {
this.parentParameters.forEach((param: ReportParameter) => {
this.ReportForm.get(param.name).valueChanges.subscribe((option: any) => {
param.childParameter.forEach((child: ReportParameter) => {
if (child.displayType === 'none') {
this.ReportForm.addControl(child.name, new FormControl(child.defaultVal));
} else {
this.ReportForm.addControl(child.name, new FormControl('', Validators.required));
}
if (child.displayType === 'select') {
const inputstring = `${child.name}?${param.inputName}=${option.id}`;
this.fetchSelectOptions(child, inputstring);
}
});
});
});
}
/**
* Fetches Select Dropdown options for param type "Select".
* @param {ReportParameter} param Parameter for which dropdown options are required.
* @param {string} inputstring url substring for API call.
*/
fetchSelectOptions(param: ReportParameter, inputstring: string) {
this.reportsService.getSelectOptions(inputstring).subscribe((options: SelectOption[]) => {
param.selectOptions = options;
if (param.selectAll === 'Y') {
param.selectOptions.push({id: '-1', name: 'All'});
}
const optionId = this.paramValue[param.variable].toString();
const option = options.find(entry => entry.id === optionId);
this.ReportForm.controls[param.name].patchValue({ id: optionId, name: option.name });
});
}
/**
* Compare function for mat-select.
* Useful in patching values if value is an object.
* @param {any} option1 option 1
* @param {any} option2 option 2.
*/
compareOptions(option1: any, option2: any) {
return option1 && option2 && option1.id === option2.id;
}
/**
* Disable the Report Form once all values are patched.
*/
disableFormWhenValid() {
this.ReportForm.statusChanges.pipe(distinctUntilChanged()).subscribe((status: string) => {
if (status === 'VALID') {
this.ReportForm.disable();
}
});
}
/**
* Formats user response and readies it for utilization by run report function.
* @param {any} response Object containing formcontrol values.
*/
formatUserResponse(response: any, forHeaders: boolean) {
const formattedResponse: any = {};
let newKey: string;
for (const [key, value] of Object.entries(response)) {
const param: ReportParameter = this.paramData
.find((_entry: any) => _entry.variable === key);
newKey = forHeaders ? param.inputName : param.variable;
formattedResponse[newKey] = value;
}
return formattedResponse;
}
/**
* Gets run report response headers.
* Emits them via template parameters event emitter.
* TODO: Replace report object with report name once reports service is refactored.
*/
getResponseHeaders() {
const report = { name: this.paramValue.reportName };
delete this.paramValue.reportName;
const formattedResponse = this.formatUserResponse(this.paramValue, true);
this.reportsService.getRunReportData(report, formattedResponse).subscribe(
(response: any) => {
this.templateParameters.emit(response.columnHeaders);
},
(error: any) => {
this.templateParameters.emit(null);
this.ReportForm.disable();
}
);
}
}
@@ -0,0 +1,67 @@
<form [formGroup]="smsCampaignDetailsForm">
<div fxLayout="row wrap" fxLayoutGap="2%" fxLayout.lt-md="column" fxLayoutAlign.gt-sm="start center">
<mat-form-field fxFlex="48%">
<mat-label>Campaign Name</mat-label>
<input matInput formControlName="campaignName" required>
<mat-error>
Campaign Name is <strong>required</strong>
</mat-error>
</mat-form-field>
<mat-form-field fxFlex="48%" *ngIf="!smsCampaignDetailsForm.controls.isNotification.value">
<mat-label>SMS Provider</mat-label>
<mat-select formControlName="providerId">
<mat-option *ngFor="let provider of smsProviders" [value]="provider.id">
{{ provider.value }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field fxFlex="48%">
<mat-label>Trigger Type</mat-label>
<mat-select formControlName="triggerType" required>
<mat-option *ngFor="let triggerType of triggerTypes" [value]="triggerType.id">
{{ triggerType.value }}
</mat-option>
</mat-select>
<mat-error>
Trigger Type is <strong>required</strong>
</mat-error>
</mat-form-field>
<mat-checkbox labelPosition="before" formControlName="isNotification" fxFlex="48%">
is Notification?
</mat-checkbox>
<mat-form-field fxFlex="48%" *ngIf="smsCampaignDetailsForm.controls.recurrenceStartDate">
<mat-label>Schedule Date</mat-label>
<input matInput [min]="minDate" [max]="maxDate" [matDatepicker]="recurrenceStartDatePicker"
formControlName="recurrenceStartDate" required>
<mat-datepicker-toggle matSuffix [for]="recurrenceStartDatePicker"></mat-datepicker-toggle>
<mat-datepicker #recurrenceStartDatePicker></mat-datepicker>
<mat-error>
Schedule Date is <strong>required</strong>
</mat-error>
</mat-form-field>
<mat-form-field fxFlex="48%">
<mat-label>Business Rule</mat-label>
<mat-select formControlName="runReportId" required>
<mat-option *ngFor="let rule of businessRules" [value]="rule.reportId">
{{ rule.reportName }}
</mat-option>
</mat-select>
<mat-error>
Business Rule is <strong>required</strong>
</mat-error>
</mat-form-field>
</div>
<mifosx-edit-business-rule-parameters [paramData]="paramData" [smsCampaign]="smsCampaign"
(templateParameters)="passParameters($event)">
</mifosx-edit-business-rule-parameters>
</form>
@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { EditSmsCampaignStepComponent } from './edit-sms-campaign-step.component';
describe('EditSmsCampaignStepComponent', () => {
let component: EditSmsCampaignStepComponent;
let fixture: ComponentFixture<EditSmsCampaignStepComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ EditSmsCampaignStepComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(EditSmsCampaignStepComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,106 @@
/** Angular Imports */
import { Component, OnInit, Output, Input, EventEmitter } from '@angular/core';
import { FormGroup, Validators, FormBuilder, FormControl } from '@angular/forms';
/** Custom Services */
import { ReportsService } from 'app/reports/reports.service';
/** Custom Models */
import { ReportParameter } from 'app/reports/common-models/report-parameter.model';
/**
* Edit SMS Campaign step.
*/
@Component({
selector: 'mifosx-edit-sms-campaign-step',
templateUrl: './edit-sms-campaign-step.component.html',
styleUrls: ['./edit-sms-campaign-step.component.scss']
})
export class EditSmsCampaignStepComponent implements OnInit {
/** SMS Campaign Template */
@Input() smsCampaignTemplate: any;
/** SMS Campaign */
@Input() smsCampaign: any;
/** SMS Campaign Form */
smsCampaignDetailsForm: FormGroup;
/** Data to be passed to sub component */
paramData: any;
/** Trigger types options */
triggerTypes: any[];
/** SMS providers options */
smsProviders: any[];
/** Business Rules options */
businessRules: any[];
/** Repetition Intervals */
repetitionIntervals: any[];
/** Template Parameters Event Emitter */
@Output() templateParameters = new EventEmitter();
/**
* @param {FormBuilder} formBuilder Form Builder
* @param {ReportsService} reportService Reports Service
*/
constructor(private formBuilder: FormBuilder,
private reportService: ReportsService) {
this.createSMSCampaignDetailsForm();
}
/**
* Initializes the SMS campaign form.
*/
createSMSCampaignDetailsForm() {
this.smsCampaignDetailsForm = this.formBuilder.group({
'campaignName': ['', Validators.required],
'providerId': [null],
'triggerType': ['', Validators.required],
'runReportId': ['', Validators.required],
'isNotification': [false]
});
}
ngOnInit() {
this.triggerTypes = this.smsCampaignTemplate.triggerTypeOptions;
this.smsProviders = this.smsCampaignTemplate.smsProviderOptions;
this.businessRules = this.smsCampaignTemplate.businessRulesOptions;
this.setControlValues();
this.getParameters();
}
/**
* Passes template parameters emitted from child to parent.
* @param {any} $event Template Parameters
*/
passParameters($event: any) {
this.templateParameters.emit($event);
}
/**
* Gets Template parameters and disables the SMS form.
*/
getParameters() {
this.reportService.getReportParams(this.smsCampaign.reportName).subscribe((response: ReportParameter[]) => {
this.paramData = response;
});
this.smsCampaignDetailsForm.disable();
}
/**
* Patches all control values as in API response.
*/
setControlValues() {
this.smsCampaignDetailsForm.patchValue({
'campaignName': this.smsCampaign.campaignName,
'providerId': this.smsCampaign.providerId,
'triggerType': this.smsCampaign.triggerType.id,
'runReportId': this.smsCampaign.runReportId,
'isNotification': this.smsCampaign.isNotification
});
if (this.smsCampaign.triggerType.value === 'Schedule') {
this.smsCampaignDetailsForm.addControl('recurrenceStartDate', new FormControl(new Date(this.smsCampaign.recurrenceStartDate)));
}
}
}
@@ -0,0 +1,61 @@
<div fxLayout="column">
<mat-divider fxFlex="98%"></mat-divider>
<h3 fxFlex="98%" class="mat-h3">Business Rule Parameters</h3>
<form [formGroup]="ReportForm">
<div fxLayout="row wrap" fxLayoutGap="2%">
<ng-container *ngFor="let param of paramData" [ngSwitch]="param.displayType">
<ng-container *ngIf="ReportForm.contains(param.name)">
<mat-form-field fxFlex="48%" *ngSwitchCase="'text'">
<mat-label>{{param.label}}</mat-label>
<input matInput required [formControlName]="param.name">
<mat-error *ngIf="ReportForm.controls[param.name].hasError('required')">
{{param.label}} is <strong>required</strong>
</mat-error>
</mat-form-field>
<mat-form-field fxFlex="48%" *ngSwitchCase="'date'">
<mat-label>{{param.label}}</mat-label>
<input matInput [min]="minDate" [max]="maxDate" [matDatepicker]="runReportDatePicker" required
[formControlName]="param.name">
<mat-datepicker-toggle matSuffix [for]="runReportDatePicker"></mat-datepicker-toggle>
<mat-datepicker #runReportDatePicker></mat-datepicker>
<mat-error *ngIf="ReportForm.controls[param.name].hasError('required')">
{{param.label}} is <strong>required</strong>
</mat-error>
</mat-form-field>
<mat-form-field fxFlex="48%" *ngSwitchCase="'select'">
<mat-label>{{param.label}}</mat-label>
<mat-select required [formControlName]="param.name">
<mat-option *ngFor="let option of param.selectOptions" [value]="option">
{{option.name}}
</mat-option>
</mat-select>
<mat-error *ngIf="ReportForm.controls[param.name].hasError('required')">
{{param.label}} is <strong>required</strong>
</mat-error>
</mat-form-field>
</ng-container>
</ng-container>
</div>
</form>
</div>
<div class="margin-t" fxLayoutAlign="center">
<button mat-raised-button matStepperNext color="primary" (click)="getResponseHeaders()"
[disabled]="!this.ReportForm.valid">
Get Parameters
</button>
</div>
@@ -0,0 +1,15 @@
h3 {
font-weight: 500;
}
mat-divider {
margin: 1em 0 2em 0;
}
.margin-t {
margin-top: 1em;
}
.parameter {
margin-inline: 1%;
}
@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { BusinessRuleParametersComponent } from './business-rule-parameters.component';
describe('BusinessRuleParametersComponent', () => {
let component: BusinessRuleParametersComponent;
let fixture: ComponentFixture<BusinessRuleParametersComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ BusinessRuleParametersComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(BusinessRuleParametersComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,177 @@
/** Angular Imports */
import { Component, OnChanges, Input, Output, EventEmitter } from '@angular/core';
import { Validators, FormGroup, FormControl } from '@angular/forms';
import { DatePipe } from '@angular/common';
/** Custom Services */
import { ReportsService } from 'app/reports/reports.service';
/** Custom Models */
import { ReportParameter } from 'app/reports/common-models/report-parameter.model';
import { SelectOption } from 'app/reports/common-models/select-option.model';
/**
* Business Rule Parameters Component.
*/
@Component({
selector: 'mifosx-business-rule-parameters',
templateUrl: './business-rule-parameters.component.html',
styleUrls: ['./business-rule-parameters.component.scss']
})
export class BusinessRuleParametersComponent implements OnChanges {
/** Run Report Parameters Data */
@Input() paramData: any;
/** Report Name */
reportName: string;
/** Initializes new form group ReportForm */
ReportForm = new FormGroup({});
/** Array of all parent parameters */
parentParameters: any[] = [];
/** Template Parameters Event Emitter */
@Output() templateParameters = new EventEmitter();
/**
* @param {ReportsService} reportsService Reports Service
* @param {DatePipe} datePipe Date Pipe
*/
constructor(private reportsService: ReportsService,
private datePipe: DatePipe) { }
ngOnChanges() {
if (this.paramData) {
this.ReportForm = new FormGroup({});
this.reportName = this.paramData.reportName;
this.paramData = this.paramData.response;
this.createRunReportForm();
}
}
/**
* Formatted form value for API request.
*/
get businessRuleFormValue() {
const formattedresponse = this.formatUserResponse(this.ReportForm.value, false);
formattedresponse.reportName = this.reportName;
return { paramValue: formattedresponse };
}
/**
* Establishes form controls for Report Parameter's name attribute,
* Fetches dropdown options and builds child dependencies.
*/
createRunReportForm() {
this.paramData.forEach(
(param: any) => {
if (!param.parentParameterName) { // Non Child Parameter
this.ReportForm.addControl(param.name, new FormControl('', Validators.required));
if (param.displayType === 'select') {
this.fetchSelectOptions(param, param.name);
}
} else { // Child Parameter
const parent: ReportParameter = this.paramData
.find((entry: any) => entry.name === param.parentParameterName);
// TODO: Refactor once Report Parameter Model is updated for multi-child support.
if (parent.childParameter instanceof Array) {
parent.childParameter.push(param);
} else {
parent.childParameter = [];
parent.childParameter.push(param);
}
const parentNames = this.parentParameters.map(parameter => parameter.name);
if (!parentNames.includes(parent.name)) {
this.parentParameters.push(parent);
} else {
const index = parentNames.indexOf(parent.name);
this.parentParameters[index] = parent;
}
}
});
this.setChildControls();
}
/**
* Subscribes to changes in parent parameters value, builds child parameter vis-a-vis parent's value.
*/
setChildControls() {
this.parentParameters.forEach((param: ReportParameter) => {
this.ReportForm.get(param.name).valueChanges.subscribe((option: any) => {
param.childParameter.forEach((child: ReportParameter) => {
if (child.displayType === 'none') {
this.ReportForm.addControl(child.name, new FormControl(child.defaultVal));
} else {
this.ReportForm.addControl(child.name, new FormControl('', Validators.required));
}
if (child.displayType === 'select') {
const inputstring = `${child.name}?${param.inputName}=${option.id}`;
this.fetchSelectOptions(child, inputstring);
}
});
});
});
}
/**
* Fetches Select Dropdown options for param type "Select".
* @param {ReportParameter} param Parameter for which dropdown options are required.
* @param {string} inputstring url substring for API call.
*/
fetchSelectOptions(param: ReportParameter, inputstring: string) {
this.reportsService.getSelectOptions(inputstring).subscribe((options: SelectOption[]) => {
param.selectOptions = options;
if (param.selectAll === 'Y') {
param.selectOptions.push({id: '-1', name: 'All'});
}
});
}
/**
* Formats user response and readies it for utilization by run report function.
* @param {any} response Object containing formcontrol values.
*/
formatUserResponse(response: any, forHeaders: boolean) {
const formattedResponse: any = {};
let newKey: string;
for (const [key, value] of Object.entries(response)) {
const param: ReportParameter = this.paramData
.find((_entry: any) => _entry.name === key);
newKey = forHeaders ? param.inputName : param.variable;
switch (param.displayType) {
case 'text':
formattedResponse[newKey] = value;
break;
case 'select':
formattedResponse[newKey] = value['id'];
break;
case 'date':
const dateFormat = 'yyyy-MM-dd';
formattedResponse[newKey] = this.datePipe.transform(value, dateFormat);
break;
case 'none':
formattedResponse[newKey] = value;
break;
}
}
return formattedResponse;
}
/**
* Gets run report response headers.
* Emits them via template parameters event emitter.
* TODO: Replace report object with report name once reports service is refactored.
*/
getResponseHeaders() {
const formattedresponse = this.formatUserResponse(this.ReportForm.value, true);
this.reportsService.getRunReportData({ name: this.reportName }, formattedresponse).subscribe(
(response: any) => {
this.templateParameters.emit(response.columnHeaders);
},
(error: any) => {
this.templateParameters.emit(null);
}
);
}
}
@@ -0,0 +1,128 @@
<form [formGroup]="smsCampaignDetailsForm">
<div fxLayout="row wrap" fxLayoutGap="2%" fxLayout.lt-md="column" fxLayoutAlign.gt-sm="start center">
<mat-form-field fxFlex="48%">
<mat-label>Campaign Name</mat-label>
<input matInput formControlName="campaignName" required>
<mat-error>
Campaign Name is <strong>required</strong>
</mat-error>
</mat-form-field>
<mat-form-field fxFlex="48%"
*ngIf="!smsCampaignDetailsForm.controls.isNotification.value">
<mat-label>SMS Provider</mat-label>
<mat-select formControlName="providerId">
<mat-option *ngFor="let provider of smsProviders" [value]="provider.id">
{{ provider.value }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field fxFlex="48%">
<mat-label>Trigger Type</mat-label>
<mat-select formControlName="triggerType" required>
<mat-option *ngFor="let triggerType of triggerTypes" [value]="triggerType.id">
{{ triggerType.value }}
</mat-option>
</mat-select>
<mat-error>
Trigger Type is <strong>required</strong>
</mat-error>
</mat-form-field>
<mat-checkbox labelPosition="before" formControlName="isNotification" fxFlex="48%">
is Notification?
</mat-checkbox>
<mat-form-field fxFlex="48%"
*ngIf="smsCampaignDetailsForm.contains('recurrenceStartDate')">
<mat-label>Schedule Date</mat-label>
<input matInput [min]="minDate" [max]="maxDate" [matDatepicker]="recurrenceStartDatePicker"
formControlName="recurrenceStartDate" required>
<mat-datepicker-toggle matSuffix [for]="recurrenceStartDatePicker"></mat-datepicker-toggle>
<mat-datepicker #recurrenceStartDatePicker></mat-datepicker>
<mat-error>
Schedule Date is <strong>required</strong>
</mat-error>
</mat-form-field>
<mat-form-field fxFlex="48%"
*ngIf="smsCampaignDetailsForm.contains('frequency')">
<mat-label>Repeats</mat-label>
<mat-select formControlName="frequency" required>
<mat-option [value]="1">Daily</mat-option>
<mat-option [value]="2">Weekly</mat-option>
<mat-option [value]="3">Monthly</mat-option>
<mat-option [value]="4">Yearly</mat-option>
</mat-select>
<mat-error>
Repetition Frequency is <strong>required</strong>
</mat-error>
</mat-form-field>
<mat-form-field fxFlex="48%"
*ngIf="smsCampaignDetailsForm.contains('interval')">
<mat-label>Repetition Interval</mat-label>
<mat-select formControlName="interval" required>
<mat-option *ngFor="let interval of repetitionIntervals" [value]="interval">
{{ interval }}
</mat-option>
</mat-select>
<mat-error>
Repetition Interval is <strong>required</strong>
</mat-error>
</mat-form-field>
<mat-form-field fxFlex="48%"
*ngIf="smsCampaignDetailsForm.contains('repeatsOnDay')">
<mat-label>Repeats on Day</mat-label>
<mat-select formControlName="repeatsOnDay" required>
<mat-option value="1">Monday</mat-option>
<mat-option value="2">Tuesday</mat-option>
<mat-option value="3">Wednesday</mat-option>
<mat-option value="4">Thursday</mat-option>
<mat-option value="5">Friday</mat-option>
<mat-option value="6">Saturday</mat-option>
<mat-option value="7">Sunday</mat-option>
</mat-select>
<mat-error>
At least <strong>one</strong> day must be selected
</mat-error>
</mat-form-field>
<mat-form-field fxFlex="48%">
<mat-label>Business Rule</mat-label>
<mat-select formControlName="runReportId" required>
<mat-option *ngFor="let rule of businessRules" [value]="rule.reportId">
{{ rule.reportName }}
</mat-option>
</mat-select>
<mat-error>
Business Rule is <strong>required</strong>
</mat-error>
</mat-form-field>
</div>
<div fxLayout="row" class="margin-t" fxLayout.xs="column" fxLayoutAlign="center" fxLayoutGap="2%"
*ngIf="!smsCampaignDetailsForm.controls.runReportId.value">
<button mat-raised-button matStepperPrevious disabled>
<fa-icon icon="arrow-left"></fa-icon>&nbsp;&nbsp;
Previous
</button>
<button mat-raised-button matStepperNext>
Next&nbsp;&nbsp;
<fa-icon icon="arrow-right"></fa-icon>
</button>
</div>
<mifosx-business-rule-parameters
*ngIf="smsCampaignDetailsForm.controls.runReportId.value"
[paramData]="paramData"
(templateParameters)="passParameters($event)"
>
</mifosx-business-rule-parameters>
</form>
@@ -0,0 +1,3 @@
.margin-t {
margin-top: 1em;
}
@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { SmsCampaignStepComponent } from './sms-campaign-step.component';
describe('SmsCampaignStepComponent', () => {
let component: SmsCampaignStepComponent;
let fixture: ComponentFixture<SmsCampaignStepComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ SmsCampaignStepComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(SmsCampaignStepComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,185 @@
/** Angular Imports */
import { Component, OnInit, Input, ViewChild, EventEmitter, Output} from '@angular/core';
import { FormGroup, FormBuilder, Validators, FormControl } from '@angular/forms';
/** Custom Services */
import { ReportsService } from 'app/reports/reports.service';
/** Custom Models */
import { ReportParameter } from 'app/reports/common-models/report-parameter.model';
/** Custom Components */
import { BusinessRuleParametersComponent } from './business-rule-parameters/business-rule-parameters.component';
/**
* SMS Campaign Step Component
*/
@Component({
selector: 'mifosx-sms-campaign-step',
templateUrl: './sms-campaign-step.component.html',
styleUrls: ['./sms-campaign-step.component.scss']
})
export class SmsCampaignStepComponent implements OnInit {
/** SMS Campaign Template */
@Input() smsCampaignTemplate: any;
/** Business Rule Parameters Component */
@ViewChild(BusinessRuleParametersComponent) businessRuleParametersComponent: BusinessRuleParametersComponent;
/** Min. Date */
minDate = new Date();
/** Max. Date */
maxDate = new Date(new Date().setFullYear(new Date().getFullYear() + 10));
/** SMS Campaign Form */
smsCampaignDetailsForm: FormGroup;
/** Data to be passed to sub component */
paramData: any;
/** Trigger types options */
triggerTypes: any[];
/** SMS providers options */
smsProviders: any[];
/** Business Rules options */
businessRules: any[];
/** Repetition Intervals */
repetitionIntervals: any[];
/** Template Parameters Event Emitter */
@Output() templateParameters = new EventEmitter();
/**
* @param {FormBuilder} formBuilder Form Builder
* @param {ReportsService} reportService Reports Service
*/
constructor(private formBuilder: FormBuilder,
private reportService: ReportsService) {
this.createSMSCampaignDetailsForm();
this.buildDependencies();
}
/**
* Sets SMS providers and trigger types options.
*/
ngOnInit() {
this.triggerTypes = this.smsCampaignTemplate.triggerTypeOptions;
this.smsProviders = this.smsCampaignTemplate.smsProviderOptions;
}
/**
* Returns SMS Campaign Details Form if Business Rule is unassigned
* Else returns a cumulative form group.
*/
get smsCampaignFormGroup() {
let smsCampaignFormGroup: FormGroup;
if (this.businessRuleParametersComponent) {
smsCampaignFormGroup = new FormGroup({
smsCampaign: this.smsCampaignDetailsForm,
businessRule: this.businessRuleParametersComponent.ReportForm
});
} else {
smsCampaignFormGroup = new FormGroup({
smsCampaign: this.smsCampaignDetailsForm
});
}
return smsCampaignFormGroup;
}
/**
* Returns SMS Campaign Details Form value if Business Rule is unassigned
* Else returns cumulative form group value.
*/
get smsCampaignFormGroupValue() {
if (this.businessRuleParametersComponent) {
return {
...this.smsCampaignDetailsForm.value,
...this.businessRuleParametersComponent.businessRuleFormValue
};
} else {
return this.smsCampaignDetailsForm.value;
}
}
/**
* Passes template parameters emitted from child to parent.
* @param {any} $event Template Parameters
*/
passParameters($event: any) {
this.templateParameters.emit($event);
}
/**
* Initializes the SMS campaign form.
*/
createSMSCampaignDetailsForm() {
this.smsCampaignDetailsForm = this.formBuilder.group({
'campaignName': ['', Validators.required],
'providerId': [null],
'triggerType': ['', Validators.required],
'runReportId': ['', Validators.required],
'isNotification': [false]
});
}
/**
* Sets conditional form controls and values.
* Gets reports parameters and passes it to subcomponent on business rule value changes.
*/
buildDependencies() {
this.smsCampaignDetailsForm.get('isNotification').valueChanges.subscribe((value: boolean) => {
if (!value) {
this.smsCampaignDetailsForm.addControl('providerId', new FormControl(null));
} else {
this.smsCampaignDetailsForm.removeControl('providerId');
}
});
this.smsCampaignDetailsForm.get('runReportId').valueChanges.subscribe((value: number) => {
if (value) {
const report = this.businessRules.find((rule: any) => rule.reportId === value);
this.reportService.getReportParams(report.reportName).subscribe((response: ReportParameter[]) => {
this.paramData = { response, reportName: report.reportName };
});
}
});
this.smsCampaignDetailsForm.get('triggerType').valueChanges.subscribe((value: number) => {
this.templateParameters.emit(null);
this.businessRules = this.smsCampaignTemplate.businessRulesOptions;
if (this.smsCampaignDetailsForm.controls.runReportId.value) {
this.smsCampaignDetailsForm.get('runReportId').patchValue('');
}
if (value === 3) {
this.businessRules = this.businessRules.filter((rule: any) => rule.reportSubType === 'Triggered');
} else {
this.businessRules = this.businessRules.filter((rule: any) => rule.reportSubType !== 'Triggered');
}
if (value === 2) {
this.smsCampaignDetailsForm.addControl('recurrenceStartDate', new FormControl('', Validators.required));
this.smsCampaignDetailsForm.addControl('frequency', new FormControl('', Validators.required));
this.smsCampaignDetailsForm.addControl('interval', new FormControl('', Validators.required));
this.smsCampaignDetailsForm.get('frequency').valueChanges.subscribe((frequency: number) => {
this.smsCampaignDetailsForm.removeControl('repeatsOnDay');
switch (frequency) {
case 1: // Daily
this.repetitionIntervals = ['1', '2', '3'];
break;
case 2: // Weekly
this.repetitionIntervals = ['1', '2', '3'];
this.smsCampaignDetailsForm.addControl('repeatsOnDay', new FormControl('', Validators.required));
break;
case 3: // Monthly
this.repetitionIntervals = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11'];
break;
case 4: // Yearly
this.repetitionIntervals = ['1', '2', '3', '4', '5'];
break;
}
});
} else {
this.smsCampaignDetailsForm.removeControl('recurrenceStartDate');
this.smsCampaignDetailsForm.removeControl('frequency');
this.smsCampaignDetailsForm.removeControl('interval');
this.smsCampaignDetailsForm.removeControl('repeatsOnDay');
}
});
}
}
@@ -1,6 +1,6 @@
<div class="container m-b-20" fxLayout="row" fxLayoutAlign="end" fxLayoutGap="20px">
<button mat-raised-button color="primary">
<button mat-raised-button color="primary" [routerLink]="['create']">
<fa-icon icon="plus"></fa-icon>&nbsp;&nbsp;
Create SMS Campaign
</button>
@@ -1,125 +1,151 @@
<div class="container">
<div fxLayout="row" fxLayoutGap="1%" fxLayoutAlign="flex-end" class="action-buttons m-b-20">
<span *ngIf="smsCampaignData.campaignStatus.value !== 'active'">
<button mat-raised-button color="primary" *mifosxHasPermission="'UPDATE_SMSCAMPAIGN'">
<fa-icon icon="edit"></fa-icon>&nbsp;&nbsp;Edit
</button>
</span>
<span *ngIf="smsCampaignData.campaignStatus.value === 'Pending'">
<button mat-raised-button color="accent" *mifosxHasPermission="'ACTIVATE_SMSCAMPAIGN'">
<fa-icon icon="lock-open"></fa-icon>&nbsp;&nbsp;Activate
</button>
</span>
<span *ngIf="smsCampaignData.campaignStatus.value !== 'closed'">
<button mat-raised-button color="warn" *mifosxHasPermission="'CLOSE_SMSCAMPAIGN'">
<fa-icon icon="times"></fa-icon>&nbsp;&nbsp;Close
</button>
</span>
<span *ngIf="smsCampaignData.campaignStatus.value !== 'Pending' && smsCampaignData.campaignStatus.value !== 'active'">
<button mat-raised-button color="accent" *mifosxHasPermission="'REACTIVATE_SMSCAMPAIGN'">
<fa-icon icon="undo"></fa-icon>&nbsp;&nbsp;Reactivate
</button>
</span>
<span *ngIf="smsCampaignData.campaignStatus.value === 'closed'">
<button mat-raised-button color="warn" *mifosxHasPermission="'DELETE_SMSCAMPAIGN'">
<fa-icon icon="trash"></fa-icon>&nbsp;&nbsp;Delete
</button>
</span>
</div>
<div fxLayout="row" fxLayoutGap="1%" fxLayoutAlign="flex-end" class="action-buttons m-b-20">
<mat-card class="sms-card">
<span *ngIf="smsCampaignData.campaignStatus.value !== 'active'">
<button mat-raised-button color="primary" *mifosxHasPermission="'UPDATE_SMSCAMPAIGN'" [routerLink]="['edit']">
<fa-icon icon="edit"></fa-icon>&nbsp;&nbsp;Edit
</button>
</span>
<mat-card-content>
<span *ngIf="smsCampaignData.campaignStatus.value === 'Pending'">
<button mat-raised-button color="accent" *mifosxHasPermission="'ACTIVATE_SMSCAMPAIGN'">
<fa-icon icon="lock-open"></fa-icon>&nbsp;&nbsp;Activate
</button>
</span>
<mat-tab-group (selectedTabChange)="onTabChange($event)">
<span *ngIf="smsCampaignData.campaignStatus.value !== 'closed'">
<button mat-raised-button color="warn" *mifosxHasPermission="'CLOSE_SMSCAMPAIGN'">
<fa-icon icon="times"></fa-icon>&nbsp;&nbsp;Close
</button>
</span>
<mat-tab label="Campaign">
<span *ngIf="smsCampaignData.campaignStatus.value !== 'Pending' && smsCampaignData.campaignStatus.value !== 'active'">
<button mat-raised-button color="accent" *mifosxHasPermission="'REACTIVATE_SMSCAMPAIGN'">
<fa-icon icon="undo"></fa-icon>&nbsp;&nbsp;Reactivate
</button>
</span>
<div class="tab-content mat-typography">
<mat-list>
<mat-list-item>
Campaign Name : {{ smsCampaignData.campaignName }}
</mat-list-item>
<mat-list-item>
Report Name : {{ smsCampaignData.reportName }}
</mat-list-item>
<mat-list-item>
Status : {{ smsCampaignData.campaignStatus.value }}
</mat-list-item>
<mat-list-item>
Trigger Type : {{ smsCampaignData.triggerType.value }}
</mat-list-item>
<mat-list-item>
Submitted on : {{ smsCampaignData.smsCampaignTimeLine.submittedOnDate | date}}
</mat-list-item>
<div fxLayout="column" fxLayoutGap="10px" class="template-message">
<h3>Template Message :</h3>
<textarea matInput disabled>{{ smsCampaignData.campaignMessage}}</textarea>
</div>
</mat-list>
<span *ngIf="smsCampaignData.campaignStatus.value === 'closed'">
<button mat-raised-button color="warn" *mifosxHasPermission="'DELETE_SMSCAMPAIGN'">
<fa-icon icon="trash"></fa-icon>&nbsp;&nbsp;Delete
</button>
</span>
</div>
</div>
</mat-tab>
<mat-card class="sms-card">
<mat-tab *ngFor = "let tab of smsTabs" [label]="tab.label">
<mat-card-content>
<div class="tab-content mat-typography">
<mat-tab-group (selectedTabChange)="onTabChange($event)">
<form [formGroup]="smsForm" (ngSubmit)="search()">
<div fxLayout="row" fxLayoutGap="3%" fxLayoutAlign="center">
<mat-form-field>
<mat-label>From Date</mat-label>
<input matInput [min]="minDate" [max]="maxDate" [matDatepicker]="fromDatePicker" formControlName="fromDate">
<mat-datepicker-toggle matSuffix [for]="fromDatePicker"></mat-datepicker-toggle>
<mat-datepicker #fromDatePicker></mat-datepicker>
</mat-form-field>
<mat-form-field>
<mat-label>To Date</mat-label>
<input matInput [min]="minDate" [max]="maxDate" [matDatepicker]="toDatePicker" formControlName="toDate">
<mat-datepicker-toggle matSuffix [for]="toDatePicker"></mat-datepicker-toggle>
<mat-datepicker #toDatePicker></mat-datepicker>
</mat-form-field>
<div class="search-button" >
<button mat-raised-button color="primary">
<fa-icon icon="search"></fa-icon>&nbsp;&nbsp;Search</button>
</div>
</div>
</form>
<mat-tab label="Campaign">
<div class="tab-content mat-typography">
<div class="mat-elevation-z1 m-b-25">
<table #messageTable mat-table [dataSource]="dataSource">
<ng-container matColumnDef="Message">
<th mat-header-cell *matHeaderCellDef> Message </th>
<td mat-cell *matCellDef="let sms">{{sms.message}}</td>
</ng-container>
<ng-container matColumnDef="Status">
<th mat-header-cell *matHeaderCellDef> Status </th>
<td mat-cell *matCellDef="let sms">{{sms.status.value}}</td>
</ng-container>
<ng-container matColumnDef="Mobile No.">
<th mat-header-cell *matHeaderCellDef> Mobile No. </th>
<td mat-cell *matCellDef="let sms">{{sms.mobileNo}} </td>
</ng-container>
<ng-container matColumnDef="Campaign Name">
<th mat-header-cell *matHeaderCellDef> Campaign Name </th>
<td mat-cell *matCellDef="let sms">{{sms.campaignName}}</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
<mat-list>
<mat-list-item>
Campaign Name : {{ smsCampaignData.campaignName }}
</mat-list-item>
<mat-list-item>
Report Name : {{ smsCampaignData.reportName }}
</mat-list-item>
<mat-list-item>
Status : {{ smsCampaignData.campaignStatus.value }}
</mat-list-item>
<mat-list-item>
Trigger Type : {{ smsCampaignData.triggerType.value }}
</mat-list-item>
<mat-list-item>
Submitted on : {{ smsCampaignData.smsCampaignTimeLine.submittedOnDate | date }}
</mat-list-item>
<mat-list-item *ngIf="smsCampaignData.recurrence">
Recurrence : {{ smsCampaignData.recurrence }}
</mat-list-item>
<div fxLayout="column" fxLayoutGap="10px" class="template-message">
<h3>Template Message :</h3>
<textarea matInput disabled>{{ smsCampaignData.campaignMessage}}</textarea>
</div>
</div>
</mat-list>
</mat-tab>
</div>
</mat-tab-group>
</mat-tab>
</mat-card-content>
<mat-tab *ngFor = "let tab of smsTabs" [label]="tab.label">
</mat-card>
<div class="tab-content mat-typography">
<form [formGroup]="smsForm" (ngSubmit)="search()">
<div fxLayout="row" fxLayoutGap="3%" fxLayoutAlign="center">
<mat-form-field>
<mat-label>From Date</mat-label>
<input matInput [min]="minDate" [max]="maxDate" [matDatepicker]="fromDatePicker" formControlName="fromDate">
<mat-datepicker-toggle matSuffix [for]="fromDatePicker"></mat-datepicker-toggle>
<mat-datepicker #fromDatePicker></mat-datepicker>
</mat-form-field>
<mat-form-field>
<mat-label>To Date</mat-label>
<input matInput [min]="minDate" [max]="maxDate" [matDatepicker]="toDatePicker" formControlName="toDate">
<mat-datepicker-toggle matSuffix [for]="toDatePicker"></mat-datepicker-toggle>
<mat-datepicker #toDatePicker></mat-datepicker>
</mat-form-field>
<div class="search-button" >
<button mat-raised-button color="primary">
<fa-icon icon="search"></fa-icon>&nbsp;&nbsp;Search</button>
</div>
</div>
</form>
<table #messageTable mat-table [dataSource]="dataSource" class="mat-elevation-z1 m-b-25">
<ng-container matColumnDef="Message">
<th mat-header-cell *matHeaderCellDef> Message </th>
<td mat-cell *matCellDef="let sms">{{sms.message}}</td>
</ng-container>
<ng-container matColumnDef="Status">
<th mat-header-cell *matHeaderCellDef> Status </th>
<td mat-cell *matCellDef="let sms">{{sms.status.value}}</td>
</ng-container>
<ng-container matColumnDef="Mobile No.">
<th mat-header-cell *matHeaderCellDef> Mobile No. </th>
<td mat-cell *matCellDef="let sms">{{sms.mobileNo}} </td>
</ng-container>
<ng-container matColumnDef="Campaign Name">
<th mat-header-cell *matHeaderCellDef> Campaign Name </th>
<td mat-cell *matCellDef="let sms">{{sms.campaignName}}</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
</div>
</mat-tab>
</mat-tab-group>
</mat-card-content>
</mat-card>
</div>
@@ -1,9 +1,9 @@
.action-buttons {
width: 88.75%;
width: 90%;
}
.sms-card {
margin: 0 auto;
width: 77.5%;
width: 80%;
padding: 0;
.tab-content {
padding: 1%;