diff --git a/src/app/users/create-user/create-user.component.html b/src/app/users/create-user/create-user.component.html new file mode 100644 index 000000000..21fbfb449 --- /dev/null +++ b/src/app/users/create-user/create-user.component.html @@ -0,0 +1,131 @@ +
+ + + +
+ + + +
+ + + Username + + + Username is required + + + + + Email + + + Email is invalid + + + Email is required + + + + + First Name + + + First Name cannot begin with a special character or number + + + First Name is required + + + + + Last Name + + + Last Name cannot begin with a special character or number + + + Last Name is required + + + +
+ + Override password expiry policy + +
+ +
+ + Auto generate password + +
+ + + Password + + + Password is required + + + + + Repeat Password + + + Password is required + + + +

+ Passwords do not match +

+ + + Office + + + {{ office.name }} + + + + Office is required + + + + + Staff + + + {{ staff.displayName }} + + + + + + Roles + + + {{ role.name }} + + + + At least one role must be selected + + + +
+ +
+ + + + + + +
+ +
+ +
diff --git a/src/app/users/create-user/create-user.component.scss b/src/app/users/create-user/create-user.component.scss new file mode 100644 index 000000000..5d44678cc --- /dev/null +++ b/src/app/users/create-user/create-user.component.scss @@ -0,0 +1,13 @@ +.password-never-expires-wrapper { + padding: 17.5px 0 17.5px 0; +} + +.send-password-to-email-wrapper { + padding: 17.5px 0 17.5px 0; +} + +.reset-password-error { + color: #f44336; + font-size: 80%; + margin: 0; +} diff --git a/src/app/users/create-user/create-user.component.spec.ts b/src/app/users/create-user/create-user.component.spec.ts new file mode 100644 index 000000000..0489eff4e --- /dev/null +++ b/src/app/users/create-user/create-user.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CreateUserComponent } from './create-user.component'; + +describe('CreateUserComponent', () => { + let component: CreateUserComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ CreateUserComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(CreateUserComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/users/create-user/create-user.component.ts b/src/app/users/create-user/create-user.component.ts new file mode 100644 index 000000000..838a2a452 --- /dev/null +++ b/src/app/users/create-user/create-user.component.ts @@ -0,0 +1,117 @@ +/** Angular Imports */ +import { Component, OnInit } from '@angular/core'; +import { FormGroup, FormBuilder, FormControl, Validators } from '@angular/forms'; +import { Router, ActivatedRoute } from '@angular/router'; + +/** Custom Services */ +import { UsersService } from '../users.service'; + +/** Custom Validators */ +import { confirmPasswordValidator } from '../../login/reset-password/confirm-password.validator'; + +/** + * Create user component. + */ +@Component({ + selector: 'mifosx-create-user', + templateUrl: './create-user.component.html', + styleUrls: ['./create-user.component.scss'] +}) +export class CreateUserComponent implements OnInit { + + /** User form. */ + userForm: FormGroup; + /** Offices data. */ + officesData: any; + /** Roles data. */ + rolesData: any; + /** Staff data. */ + staffData: any; + + /** + * Retrieves the offices and roles data from `resolve`. + * @param {FormBuilder} formBuilder Form Builder. + * @param {UsersService} UsersService Users Service. + * @param {ActivatedRoute} route Activated Route. + * @param {Router} router Router for navigation. + */ + constructor(private formBuilder: FormBuilder, + private usersService: UsersService, + private route: ActivatedRoute, + private router: Router) { + this.route.data.subscribe((data: { + usersTemplate: any + }) => { + this.officesData = data.usersTemplate.allowedOffices; + this.rolesData = data.usersTemplate.availableRoles; + }); + } + + /** + * Creates the user form, sets the staff data and conditional controls of the user form. + */ + ngOnInit() { + this.createUserForm(); + this.setStaffData(); + this.setConditionalControls(); + } + + /** + * Creates the user form. + */ + createUserForm() { + this.userForm = this.formBuilder.group({ + 'username': ['', Validators.required], + 'email': ['', [Validators.required, Validators.email]], + 'firstname': ['', [Validators.required, Validators.pattern('(^[A-z]).*')]], + 'lastname': ['', [Validators.required, Validators.pattern('(^[A-z]).*')]], + 'sendPasswordToEmail': [true], + 'passwordNeverExpires': [false], + 'officeId': ['', Validators.required], + 'staffId': [''], + 'roles': ['', Validators.required] + }, { validator: confirmPasswordValidator }); + } + + /** + * Sets the staff data each time the user selects a new office + */ + setStaffData() { + this.userForm.get('officeId').valueChanges.subscribe((officeId: string) => { + this.staffData = []; + this.usersService.getStaff(officeId).subscribe((staff: any) => { + this.staffData = staff; + }); + }); + } + + /** + * Sets the conditional controls of the user form + */ + setConditionalControls() { + this.userForm.get('sendPasswordToEmail').valueChanges.subscribe((sendPasswordToEmail: boolean) => { + if (sendPasswordToEmail) { + this.userForm.removeControl('password'); + this.userForm.removeControl('repeatPassword'); + this.userForm.get('email').setValidators([Validators.required, Validators.email]); + } else { + this.userForm.addControl('password', new FormControl('', Validators.required)); + this.userForm.addControl('repeatPassword', new FormControl('', Validators.required)); + this.userForm.get('email').setValidators([Validators.email]); + } + this.userForm.get('email').updateValueAndValidity(); + }); + } + + /** + * Submits the user form and creates user, + * if successful redirects to users. + */ + submit() { + const user = this.userForm.value; + this.usersService.createUser(user).subscribe((response: any) => { + this.router.navigate(['../'], { relativeTo: this.route }); + }); + } + +} diff --git a/src/app/users/users-routing.module.ts b/src/app/users/users-routing.module.ts index 11be643c7..27cd61b30 100644 --- a/src/app/users/users-routing.module.ts +++ b/src/app/users/users-routing.module.ts @@ -10,9 +10,11 @@ import { extract } from '../core/i18n/i18n.service'; /** Custom Components */ import { UsersComponent } from './users.component'; +import { CreateUserComponent } from './create-user/create-user.component'; /** Custom Resolvers */ import { UsersResolver } from './users.resolver'; +import { UsersTemplateResolver } from './users-template.resolver'; /** Users Routes */ const routes: Routes = [ @@ -27,6 +29,14 @@ const routes: Routes = [ resolve: { users: UsersResolver } + }, + { + path: 'create', + component: CreateUserComponent, + data: { title: extract('Create User'), breadcrumb: 'Create User' }, + resolve: { + usersTemplate: UsersTemplateResolver + } } ] } @@ -42,7 +52,8 @@ const routes: Routes = [ imports: [RouterModule.forChild(routes)], exports: [RouterModule], providers: [ - UsersResolver + UsersResolver, + UsersTemplateResolver ] }) export class UsersRoutingModule { } diff --git a/src/app/users/users-template.resolver.ts b/src/app/users/users-template.resolver.ts new file mode 100644 index 000000000..774ef5c21 --- /dev/null +++ b/src/app/users/users-template.resolver.ts @@ -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 { UsersService } from './users.service'; + +/** + * Users template data resolver. + */ +@Injectable() +export class UsersTemplateResolver implements Resolve { + + /** + * @param {UsersService} usersService Users service. + */ + constructor(private usersService: UsersService) {} + + /** + * Returns the users template data. + * @returns {Observable} + */ + resolve(): Observable { + return this.usersService.getUsersTemplate(); + } + +} diff --git a/src/app/users/users.component.html b/src/app/users/users.component.html index ab77e3d3c..c40b1fac5 100644 --- a/src/app/users/users.component.html +++ b/src/app/users/users.component.html @@ -1,5 +1,5 @@
- diff --git a/src/app/users/users.module.ts b/src/app/users/users.module.ts index 0a1d47b72..d034558cb 100644 --- a/src/app/users/users.module.ts +++ b/src/app/users/users.module.ts @@ -7,6 +7,7 @@ import { UsersRoutingModule } from './users-routing.module'; /** Custom Components */ import { UsersComponent } from './users.component'; +import { CreateUserComponent } from './create-user/create-user.component'; /** * Users Module @@ -19,7 +20,8 @@ import { UsersComponent } from './users.component'; UsersRoutingModule ], declarations: [ - UsersComponent + UsersComponent, + CreateUserComponent ] }) export class UsersModule { } diff --git a/src/app/users/users.service.ts b/src/app/users/users.service.ts index f05c64cbe..9aa45e68c 100644 --- a/src/app/users/users.service.ts +++ b/src/app/users/users.service.ts @@ -1,6 +1,6 @@ /** Angular Imports */ import { Injectable } from '@angular/core'; -import { HttpClient } from '@angular/common/http'; +import { HttpClient, HttpParams } from '@angular/common/http'; /** rxjs Imports */ import { Observable } from 'rxjs'; @@ -25,4 +25,29 @@ export class UsersService { return this.http.get('/users'); } + /** + * @returns {Observable} Users template data + */ + getUsersTemplate(): Observable { + return this.http.get('/users/template'); + } + + /** + * @param {any} user User to be created. + * @returns {Observable} + */ + createUser(user: any): Observable { + return this.http.post('/users', user); + } + + /** + * @param {any} officeId ID of office to retrieve staff from. + * @returns {Observable} Staff data. + */ + getStaff(officeId: any): Observable { + const httpParams = new HttpParams() + .set('officeId', officeId.toString()); + return this.http.get('/staff', { params: httpParams }); + } + }