feat: add create survey component (#579)

Fixes: #185
This commit is contained in:
punwai
2020-08-13 08:24:06 +05:30
committed by GitHub
parent 2f3005cab8
commit 6f3b6c40e7
14 changed files with 496 additions and 7 deletions
@@ -0,0 +1,8 @@
<h1 mat-dialog-title>Cancel</h1>
<div mat-dialog-content>
<p>Are you sure you want to leave this page and lose all inputted data?</p>
</div>
<mat-dialog-actions align="end">
<button mat-raised-button mat-dialog-close>No</button>
<button mat-raised-button color="warn" [mat-dialog-close]="{ cancel: true }">Yes</button>
</mat-dialog-actions>
@@ -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<CancelDialogComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ CancelDialogComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(CancelDialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -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<CancelDialogComponent>) { }
ngOnInit() {
}
}
+2
View File
@@ -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,
+4 -1
View File
@@ -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 { }
@@ -0,0 +1,169 @@
<div class="container">
<form [formGroup]="surveyForm" (ngSubmit)="submit()">
<div fxLayout="column" fxLayoutGap="4%">
<mat-card>
<mat-card-content>
<div fxLayout="row wrap" fxLayoutGap="2%" fxLayout.lt-md="column">
<mat-form-field fxFlex="48%">
<mat-label>Key</mat-label>
<input matInput maxlength="32" required formControlName="key">
<mat-error *ngIf="surveyForm.controls.key.hasError('required')">
Key is <strong>required</strong>
</mat-error>
</mat-form-field>
<mat-form-field fxFlex="48%">
<mat-label>Name</mat-label>
<input matInput maxlength="255" required formControlName="name">
<mat-error *ngIf="surveyForm.controls.name.hasError('required')">
Name is <strong>required</strong>
</mat-error>
</mat-form-field>
<mat-form-field fxFlex="48%">
<mat-label>Country Code</mat-label>
<input matInput maxlength="2" required formControlName="countryCode">
<mat-error *ngIf="surveyForm.controls.countryCode.hasError('required')">
Country Code is <strong>required</strong>
</mat-error>
<mat-error *ngIf="surveyForm.controls.countryCode.hasError('pattern')">
Country Code <strong>must consist of 2 alphabetic characters</strong>
</mat-error>
</mat-form-field>
<mat-form-field fxFlex="98%">
<mat-label>Description</mat-label>
<textarea matInput formControlName="description"></textarea>
</mat-form-field>
<div fxFlexFill fxLayout="row" fxLayout.xs="column" fxLayoutAlign="center" fxLayoutGap="5px" *ngIf="!questionDatas.length">
<button type="button" mat-raised-button [routerLink]="['../']">Cancel</button>
<button type="button" mat-raised-button color="primary" [disabled]="!(this.surveyForm.get('key').valid && this.surveyForm.get('name').valid && this.surveyForm.get('countryCode').valid)" (click)="addQuestion()">
Proceed
</button>
</div>
</div>
</mat-card-content>
</mat-card>
<div cdkDropList (cdkDropListDropped)="dropQuestion($event)" fxLayout="column" fxLayoutGap="4%">
<mat-card cdkDrag cdkDragLockAxis="y" formArrayName="questionDatas" *ngFor="let question of questionDatas.controls; let questionIndex = index; last as isLast">
<mat-card-content>
<div fxFlexFill fxLayoutGap="2%" fxLayout.lt-md="column" fxLayout="row wrap" [formGroupName]="questionIndex">
<div fxFlex="98%" fxLayout="row wrap" fxLayoutGap="2%" fxLayoutAlign="space-between center">
<h2 class="mat-h2">Question {{ questionIndex + 1 }}</h2>
<button mat-raised-button color="warn" (click)="removeQuestion(questionIndex)" [disabled]="questionDatas.controls.length === 1">
<fa-icon icon="trash"></fa-icon>&nbsp;&nbsp;
Delete Question
</button>
</div>
<mat-form-field fxFlex="48%">
<mat-label>Key</mat-label>
<input matInput maxlength="32" required formControlName="key">
<mat-error *ngIf="question.controls.key.hasError('required')">
Key is <strong>required</strong>
</mat-error>
</mat-form-field>
<mat-form-field fxFlex="48">
<mat-label>Text</mat-label>
<input matInput maxlength="255" required formControlName="text">
<mat-error *ngIf="question.controls.text.hasError('required')">
Text is <strong>required</strong>
</mat-error>
</mat-form-field>
<mat-form-field fxFlex="98%">
<mat-label>Description</mat-label>
<textarea matInput formControlName="description"></textarea>
</mat-form-field>
<mat-divider [inset]="true"></mat-divider>
<div fxFlex="98%" fxLayout="row" fxLayoutAlign="space-between center">
<h4 class="mat-h4">Options</h4>
<button type="button" mat-raised-button color="primary" (click)="addResponse(questionIndex)">
<fa-icon icon="plus"></fa-icon>&nbsp;&nbsp;
Add Option
</button>
</div>
<div cdkDropList (cdkDropListDropped)="dropResponse($event, questionIndex)" fxFlexFill fxLayout="row wrap" fxLayoutGap="2%">
<div cdkDrag cdkDragLockAxis="y" fxFlexFill fxLayout="row wrap" fxLayoutGap="2%" formArrayName="responseDatas" *ngFor="let response of getResponseDatas(questionIndex).controls; let responseIndex = index;">
<div fxFlexFill fxLayout="row wrap" fxLayoutGap="2%" [formGroupName]="responseIndex">
<mat-form-field fxFlex="43%">
<mat-label>Text</mat-label>
<input matInput required formControlName="text">
<mat-error *ngIf="response.controls.text.hasError('required')">
Text is <strong>required</strong>
</mat-error>
</mat-form-field>
<mat-form-field fxFlex="43%">
<mat-label>Value</mat-label>
<input matInput required formControlName="value">
<mat-error *ngIf="response.controls.value.hasError('required')">
Value is <strong>required</strong>
</mat-error>
<mat-error *ngIf="response.controls.value.hasError('pattern')">
Value <strong>must be an integer between -9999 and 9999</strong>
</mat-error>
</mat-form-field>
<div fxFlex="8%">
<div class="delete-wrapper">
<button type="button" color="warn" mat-icon-button matTooltip="Delete" matTooltipPosition="above" (click)="removeResponse(getResponseDatas(questionIndex), responseIndex)" [disabled]="getResponseDatas(questionIndex).controls.length === 1">
<fa-icon icon="trash" size="lg"></fa-icon>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</mat-card-content>
<div>
<mat-card-actions *ngIf="isLast" fxLayout="row" fxLayout.xs="column" fxLayoutAlign="center" fxLayoutGap="5px">
<button type="button" mat-raised-button (click)="cancelSurvey()">Cancel</button>
<button type="button" mat-raised-button color="primary"(click)="addQuestion()">
<fa-icon icon="plus"></fa-icon>&nbsp;&nbsp;
Add Question
</button>
<button mat-raised-button color="primary" [disabled]="!surveyForm.valid">Submit</button>
</mat-card-actions>
</div>
</mat-card>
</div>
</div>
</form>
</div>
@@ -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;
}
@@ -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<CreateSurveyComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ CreateSurveyComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(CreateSurveyComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -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<string[]>) {
moveItemInArray(this.questionDatas.controls, event.previousIndex, event.currentIndex);
this.updateSequenceNumber();
}
/**
* Reorders the response order according to the user drop
*/
dropResponse(event: CdkDragDrop<string[]>, 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 });
});
}
}
@@ -1,5 +1,5 @@
<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 Surveys
</button>
+15 -4
View File
@@ -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',
+3 -1
View File
@@ -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,
+8
View File
@@ -213,6 +213,14 @@ export class SystemService {
return this.http.get('/surveys');
}
/**
* @param {any} survey Survey to be created.
* @returns {Observable<any>}
*/
createSurvey(survey: any): Observable<any> {
return this.http.post('/surveys', survey);
}
/**
* @returns {Observable<any>} Fetches Jobs.
*/