diff --git a/src/app/shared/cancel-dialog/cancel-dialog.component.html b/src/app/shared/cancel-dialog/cancel-dialog.component.html new file mode 100644 index 000000000..a98c6c5e3 --- /dev/null +++ b/src/app/shared/cancel-dialog/cancel-dialog.component.html @@ -0,0 +1,8 @@ +

Cancel

+
+

Are you sure you want to leave this page and lose all inputted data?

+
+ + + + diff --git a/src/app/shared/cancel-dialog/cancel-dialog.component.scss b/src/app/shared/cancel-dialog/cancel-dialog.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/app/shared/cancel-dialog/cancel-dialog.component.spec.ts b/src/app/shared/cancel-dialog/cancel-dialog.component.spec.ts new file mode 100644 index 000000000..94decaa50 --- /dev/null +++ b/src/app/shared/cancel-dialog/cancel-dialog.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CancelDialogComponent } from './cancel-dialog.component'; + +describe('CancelDialogComponent', () => { + let component: CancelDialogComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ CancelDialogComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(CancelDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/shared/cancel-dialog/cancel-dialog.component.ts b/src/app/shared/cancel-dialog/cancel-dialog.component.ts new file mode 100644 index 000000000..937071198 --- /dev/null +++ b/src/app/shared/cancel-dialog/cancel-dialog.component.ts @@ -0,0 +1,23 @@ +/** Angular Imports */ +import { Component, OnInit, Inject } from '@angular/core'; +import { MatDialogRef } from '@angular/material'; + +/** + * Cancel dialog component. + */ +@Component({ + selector: 'mifosx-cancel-dialog', + templateUrl: './cancel-dialog.component.html', + styleUrls: ['./cancel-dialog.component.scss'] +}) +export class CancelDialogComponent implements OnInit { + + /** + * @param {MatDialogRef} dialogRef Component reference to dialog. + */ + constructor(public dialogRef: MatDialogRef) { } + + ngOnInit() { + } + +} diff --git a/src/app/shared/material.module.ts b/src/app/shared/material.module.ts index 25b7fd88d..dc4d9d482 100644 --- a/src/app/shared/material.module.ts +++ b/src/app/shared/material.module.ts @@ -1,6 +1,7 @@ /** Angular Imports */ import { NgModule } from '@angular/core'; import { LayoutModule } from '@angular/cdk/layout'; +import { DragDropModule } from '@angular/cdk/drag-drop'; import { STEPPER_GLOBAL_OPTIONS } from '@angular/cdk/stepper'; import { FlexLayoutModule } from '@angular/flex-layout'; import { @@ -48,6 +49,7 @@ import { */ @NgModule({ exports: [ + DragDropModule, FlexLayoutModule, LayoutModule, MatAutocompleteModule, diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index 2e23e0cbb..fa97cf891 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -7,6 +7,7 @@ import { ReactiveFormsModule, FormsModule } from '@angular/forms'; import { FormfieldComponent } from './form-dialog/formfield/formfield.component'; import { FormDialogComponent } from './form-dialog/form-dialog.component'; import { DeleteDialogComponent } from './delete-dialog/delete-dialog.component'; +import { CancelDialogComponent } from './cancel-dialog/cancel-dialog.component'; import { FileUploadComponent } from './file-upload/file-upload.component'; import { FooterComponent } from './footer/footer.component'; import { LanguageSelectorComponent } from './language-selector/language-selector.component'; @@ -37,6 +38,7 @@ import { MaterialModule } from './material.module'; FormfieldComponent, FormDialogComponent, DeleteDialogComponent, + CancelDialogComponent, FileUploadComponent, FooterComponent, LanguageSelectorComponent, @@ -65,7 +67,8 @@ import { MaterialModule } from './material.module'; EnableDialogComponent, DisableDialogComponent, ConfirmationDialogComponent, - KeyboardShortcutsDialogComponent + KeyboardShortcutsDialogComponent, + CancelDialogComponent ] }) export class SharedModule { } diff --git a/src/app/system/manage-surveys/create-survey/create-survey.component.html b/src/app/system/manage-surveys/create-survey/create-survey.component.html new file mode 100644 index 000000000..031aa7a2a --- /dev/null +++ b/src/app/system/manage-surveys/create-survey/create-survey.component.html @@ -0,0 +1,169 @@ +
+ +
+ +
+ + + + + +
+ + + Key + + + Key is required + + + + + Name + + + Name is required + + + + + Country Code + + + Country Code is required + + + Country Code must consist of 2 alphabetic characters + + + + + Description + + + +
+ + +
+ +
+ +
+ +
+ +
+ + + + + +
+ +
+

Question {{ questionIndex + 1 }}

+ +
+ + + Key + + + Key is required + + + + + Text + + + Text is required + + + + + Description + + + + + +
+

Options

+ +
+ +
+ +
+ +
+ + + Text + + + Text is required + + + + + Value + + + Value is required + + + Value must be an integer between -9999 and 9999 + + + +
+
+ +
+
+ +
+ +
+ +
+ +
+ +
+ +
+ + + + + + + +
+ +
+ +
+ +
+ +
+ +
diff --git a/src/app/system/manage-surveys/create-survey/create-survey.component.scss b/src/app/system/manage-surveys/create-survey/create-survey.component.scss new file mode 100644 index 000000000..6bf9c100a --- /dev/null +++ b/src/app/system/manage-surveys/create-survey/create-survey.component.scss @@ -0,0 +1,21 @@ +.delete-wrapper { + padding: 17.5px 0 0 0; +} + +h2 { + font-weight: 500; + margin: 0; +} + +h4 { + font-weight: 500; + margin: 0; +} + +.mat-card .mat-divider-horizontal.mat-divider-inset { + margin: 1em 0 2em 0; +} + +.cdk-drag-placeholder { + opacity: 0; +} diff --git a/src/app/system/manage-surveys/create-survey/create-survey.component.spec.ts b/src/app/system/manage-surveys/create-survey/create-survey.component.spec.ts new file mode 100644 index 000000000..80176109d --- /dev/null +++ b/src/app/system/manage-surveys/create-survey/create-survey.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CreateSurveyComponent } from './create-survey.component'; + +describe('CreateSurveyComponent', () => { + let component: CreateSurveyComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ CreateSurveyComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(CreateSurveyComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/system/manage-surveys/create-survey/create-survey.component.ts b/src/app/system/manage-surveys/create-survey/create-survey.component.ts new file mode 100644 index 000000000..01aa5d9f5 --- /dev/null +++ b/src/app/system/manage-surveys/create-survey/create-survey.component.ts @@ -0,0 +1,192 @@ +/** Angular Imports */ +import { Component, OnInit } from '@angular/core'; +import { FormGroup, FormBuilder, FormArray, Validators } from '@angular/forms'; +import { Router, ActivatedRoute } from '@angular/router'; +import { MatDialog } from '@angular/material'; +import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop'; + +/** Custom Services */ +import { SystemService } from '../../system.service'; + +/** Custom Components */ +import { CancelDialogComponent } from '../../../shared/cancel-dialog/cancel-dialog.component'; + +/** + * Create survey component. + */ +@Component({ + selector: 'mifosx-create-survey', + templateUrl: './create-survey.component.html', + styleUrls: ['./create-survey.component.scss'] +}) +export class CreateSurveyComponent implements OnInit { + + /** Survey form. */ + surveyForm: FormGroup; + + /** + * @param {FormBuilder} formBuilder Form Builder. + * @param {SystemService} systemService System Service. + * @param {ActivatedRoute} route Activated Route. + * @param {Router} router Router for navigation. + * @param {MatDialog} dialog Dialog reference. + */ + constructor(private formBuilder: FormBuilder, + private systemService: SystemService, + private route: ActivatedRoute, + private router: Router, + public dialog: MatDialog) { } + + /** + * Creates the survey form. + */ + ngOnInit() { + this.createSurveyForm(); + } + + /** + * Creates the survey form. + */ + createSurveyForm() { + this.surveyForm = this.formBuilder.group({ + 'key': ['', Validators.required], + 'name': ['', Validators.required], + 'countryCode': ['', [Validators.required, Validators.pattern('^\\s*([A-Za-z]{2})?\\s*$')]], + 'description': [''], + 'questionDatas': this.formBuilder.array([]) + }); + } + + /** + * Gets the questions form array. + * @returns {FormArray} Questions form array. + */ + get questionDatas(): FormArray { + return this.surveyForm.get('questionDatas') as FormArray; + } + + /** + * Gets the responses form array. + * @param {number} questionIndex Index of question to retrieve responses from. + * @returns {FormArray} Responses form array. + */ + getResponseDatas(questionIndex: number): FormArray { + return this.surveyForm.get(['questionDatas', questionIndex, 'responseDatas']) as FormArray; + } + + /** + * Creates a question form group + * @returns {FormGroup} Question form group. + */ + createQuestionForm(): FormGroup { + return this.formBuilder.group({ + 'key': ['', Validators.required], + 'text': ['', Validators.required], + 'description': [''], + 'responseDatas': this.formBuilder.array([this.createResponseForm()]), + 'sequenceNo': [''] + }); + } + + /** + * Adds a question to the questions form array + */ + addQuestion() { + this.questionDatas.push(this.createQuestionForm()); + this.updateSequenceNumber(); + } + + /** + * Removes the particular question form from the questions form array at given index. + * @param {number} index Array index from where question form needs to be removed. + */ + removeQuestion(index: number) { + this.questionDatas.removeAt(index); + this.updateSequenceNumber(); + } + + /** + * Creates a response form group + * @returns {FormGroup} Response form group. + */ + createResponseForm(): FormGroup { + return this.formBuilder.group({ + 'text': ['', Validators.required], + 'value': ['', [Validators.required, Validators.pattern('^\\s*[-]?\\d{0,4}\\s*$')]], + 'sequenceNo': [''] + }); + } + + /** + * Adds a response to the responses form array of the question with the given Index + * @param {number} questionIndex Index of question to add response to. + */ + addResponse(questionIndex: number) { + this.getResponseDatas(questionIndex).push(this.createResponseForm()); + this.updateSequenceNumber(); + } + + /** + * Removes the particular response form from the given responses form array at given index. + * @param {FormArray} responseFormArray Given responses form array. + * @param {number} index Array index from where response form needs to be removed. + */ + removeResponse(responseFormArray: FormArray, index: number) { + responseFormArray.removeAt(index); + this.updateSequenceNumber(); + } + + /** + * Updates the sequence numbers of questions and responses + */ + updateSequenceNumber() { + for (let questionIndex = 0; questionIndex < this.questionDatas.length; questionIndex++) { + this.questionDatas.at(questionIndex).get('sequenceNo').setValue(questionIndex + 1); + for (let responseIndex = 0; responseIndex < this.getResponseDatas(questionIndex).length; responseIndex++) { + this.getResponseDatas(questionIndex).at(responseIndex).get('sequenceNo').setValue(responseIndex + 1); + } + } + } + + /** + * Cancels the survey and redirects to surveys. + */ + cancelSurvey() { + const cancelSurveyDialogRef = this.dialog.open(CancelDialogComponent); + cancelSurveyDialogRef.afterClosed().subscribe((response: any) => { + if (response.cancel) { + this.router.navigate(['../'], { relativeTo: this.route }); + } + }); + } + + /** + * Reorders the question order according to the user drop + */ + dropQuestion(event: CdkDragDrop) { + moveItemInArray(this.questionDatas.controls, event.previousIndex, event.currentIndex); + this.updateSequenceNumber(); + } + + /** + * Reorders the response order according to the user drop + */ + dropResponse(event: CdkDragDrop, questionIndex: number) { + moveItemInArray(this.getResponseDatas(questionIndex).controls, event.previousIndex, event.currentIndex); + this.updateSequenceNumber(); + } + + /** + * Submits the survey form and creates survey, + * if successful redirects to surveys. + */ + submit() { + this.surveyForm.patchValue({ + countryCode: this.surveyForm.value.countryCode.toUpperCase() + }); + this.systemService.createSurvey(this.surveyForm.value).subscribe((response: any) => { + this.router.navigate(['../'], { relativeTo: this.route }); + }); + } + +} diff --git a/src/app/system/manage-surveys/manage-surveys.component.html b/src/app/system/manage-surveys/manage-surveys.component.html index 4ab7395a1..63f277807 100644 --- a/src/app/system/manage-surveys/manage-surveys.component.html +++ b/src/app/system/manage-surveys/manage-surveys.component.html @@ -1,5 +1,5 @@
- diff --git a/src/app/system/system-routing.module.ts b/src/app/system/system-routing.module.ts index 1785a9ba5..78de223d5 100644 --- a/src/app/system/system-routing.module.ts +++ b/src/app/system/system-routing.module.ts @@ -23,6 +23,7 @@ import { EditHookComponent } from './manage-hooks/edit-hook/edit-hook.component' import { RolesAndPermissionsComponent } from './roles-and-permissions/roles-and-permissions.component'; import { AddRoleComponent } from './roles-and-permissions/add-role/add-role.component'; import { ManageSurveysComponent } from './manage-surveys/manage-surveys.component'; +import { CreateSurveyComponent } from './manage-surveys/create-survey/create-survey.component'; import { ManageSchedulerJobsComponent } from './manage-scheduler-jobs/manage-scheduler-jobs.component'; import { GlobalConfigurationsComponent } from './global-configurations/global-configurations.component'; import { EditConfigurationComponent } from './global-configurations/edit-configuration/edit-configuration.component'; @@ -372,11 +373,21 @@ const routes: Routes = [ }, { path: 'surveys', - component: ManageSurveysComponent, - resolve: { - surveys: ManageSurveysResolver - }, data: { title: extract('Manage Surveys'), breadcrumb: 'Manage Surveys' }, + children: [ + { + path: '', + component: ManageSurveysComponent, + resolve: { + surveys: ManageSurveysResolver + } + }, + { + path: 'create', + component: CreateSurveyComponent, + data: { title: extract('Create Survey'), breadcrumb: 'Create' }, + } + ] }, { path: 'scheduler-jobs', diff --git a/src/app/system/system.module.ts b/src/app/system/system.module.ts index 4ec7cdc23..bc3628f35 100644 --- a/src/app/system/system.module.ts +++ b/src/app/system/system.module.ts @@ -20,6 +20,7 @@ import { ManageHooksComponent } from './manage-hooks/manage-hooks.component'; import { RolesAndPermissionsComponent } from './roles-and-permissions/roles-and-permissions.component'; import { AddRoleComponent } from './roles-and-permissions/add-role/add-role.component'; import { ManageSurveysComponent } from './manage-surveys/manage-surveys.component'; +import { CreateSurveyComponent } from './manage-surveys/create-survey/create-survey.component'; import { ManageSchedulerJobsComponent } from './manage-scheduler-jobs/manage-scheduler-jobs.component'; import { GlobalConfigurationsComponent } from './global-configurations/global-configurations.component'; import { EditConfigurationComponent } from './global-configurations/edit-configuration/edit-configuration.component'; @@ -112,7 +113,8 @@ import { ConfigureMakerCheckerTasksComponent } from './configure-maker-checker-t ViewRoleComponent, EditRoleComponent, EntityToEntityMappingComponent, - ConfigureMakerCheckerTasksComponent + ConfigureMakerCheckerTasksComponent, + CreateSurveyComponent ], entryComponents: [ ReportParameterDialogComponent, diff --git a/src/app/system/system.service.ts b/src/app/system/system.service.ts index 6ce312baf..406f0b069 100644 --- a/src/app/system/system.service.ts +++ b/src/app/system/system.service.ts @@ -213,6 +213,14 @@ export class SystemService { return this.http.get('/surveys'); } + /** + * @param {any} survey Survey to be created. + * @returns {Observable} + */ + createSurvey(survey: any): Observable { + return this.http.post('/surveys', survey); + } + /** * @returns {Observable} Fetches Jobs. */