diff --git a/.gitignore b/.gitignore index 60d143bdf..71b3b824f 100644 --- a/.gitignore +++ b/.gitignore @@ -41,7 +41,7 @@ yarn-error.log testem.log /typings .keys.json -**keys.ts +keys.ts src/environments/keys.ts # System Files diff --git a/package-lock.json b/package-lock.json index 6c037a3cd..c84ccd526 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18537,7 +18537,7 @@ }, "mkdirp": { "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "resolved": false, "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "dev": true, "optional": true, @@ -18713,7 +18713,7 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": false, "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true, "optional": true @@ -19528,7 +19528,7 @@ }, "mkdirp": { "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "resolved": false, "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "dev": true, "optional": true, @@ -19704,7 +19704,7 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": false, "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true, "optional": true diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 52561e721..ff150025d 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -34,6 +34,7 @@ import { LoginComponent } from './modules/login/login.component'; import { ActivityEffects } from './modules/activities-management/store/activity-management.effects'; import { ProjectEffects } from './modules/project-management/store/project.effects'; import { TechnologyEffects } from './modules/shared/store/technology.effects'; +import { ProjectTypeEffects } from './modules/customer-management/components/projects-type/store/project-type.effects'; import { reducers, metaReducers } from './reducers'; import { environment } from '../environments/environment'; import { CustomerComponent } from './modules/customer-management/pages/customer.component'; @@ -101,7 +102,7 @@ import { CustomerEffects } from './modules/customer-management/store/customer-ma maxAge: 15, // Retains last 15 states }) : [], - EffectsModule.forRoot([ProjectEffects, ActivityEffects, CustomerEffects, TechnologyEffects]), + EffectsModule.forRoot([ProjectEffects, ActivityEffects, CustomerEffects, TechnologyEffects, ProjectTypeEffects]), ], providers: [], bootstrap: [AppComponent], diff --git a/src/app/modules/customer-management/components/management-customer-projects/management-customer-projects.component.html b/src/app/modules/customer-management/components/management-customer-projects/management-customer-projects.component.html index b45e8d83c..ddb09b322 100644 --- a/src/app/modules/customer-management/components/management-customer-projects/management-customer-projects.component.html +++ b/src/app/modules/customer-management/components/management-customer-projects/management-customer-projects.component.html @@ -52,7 +52,10 @@
- +
+ + +
diff --git a/src/app/modules/customer-management/components/projects-type/components/create-project-type/create-project-type.component.html b/src/app/modules/customer-management/components/projects-type/components/create-project-type/create-project-type.component.html index ea6ad0967..9840c8590 100644 --- a/src/app/modules/customer-management/components/projects-type/components/create-project-type/create-project-type.component.html +++ b/src/app/modules/customer-management/components/projects-type/components/create-project-type/create-project-type.component.html @@ -1,13 +1,35 @@ -
-
+ +
- - - - + +
+ Name is required. +
+ +

- -
+ diff --git a/src/app/modules/customer-management/components/projects-type/components/create-project-type/create-project-type.component.spec.ts b/src/app/modules/customer-management/components/projects-type/components/create-project-type/create-project-type.component.spec.ts index e7975db2e..af3c956c9 100644 --- a/src/app/modules/customer-management/components/projects-type/components/create-project-type/create-project-type.component.spec.ts +++ b/src/app/modules/customer-management/components/projects-type/components/create-project-type/create-project-type.component.spec.ts @@ -1,14 +1,43 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { FormBuilder } from '@angular/forms'; +import { provideMockStore, MockStore } from '@ngrx/store/testing'; import { CreateProjectTypeComponent } from './create-project-type.component'; +import { + ProjectTypeState, + CreateProjectType, + UpdateProjectType, + projectTypeIdToEdit, + allProjectTypes, + ResetProjectTypeToEdit, +} from '../../store'; +import { ProjectType } from '../../../../../shared/models/project-type.model'; describe('InputProjectTypeComponent', () => { let component: CreateProjectTypeComponent; let fixture: ComponentFixture; + let store: MockStore; + let projectTypeIdToEditMock; + let allProjectTypesMock; + let getProjectTypeByIdMock; + + const state = { + data: [{ id: '', name: '', description: '' }], + isLoading: false, + message: '', + projectTypeIdToEdit: '', + }; + + const projectType: ProjectType = { + id: '1', + name: 'Training', + description: 'It is good for learning', + }; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [CreateProjectTypeComponent], + providers: [FormBuilder, provideMockStore({ initialState: state })], }).compileComponents(); })); @@ -16,9 +45,102 @@ describe('InputProjectTypeComponent', () => { fixture = TestBed.createComponent(CreateProjectTypeComponent); component = fixture.componentInstance; fixture.detectChanges(); + + store = TestBed.inject(MockStore); + store.setState(state); + }); + + afterEach(() => { + fixture.destroy(); }); it('component should be created', () => { expect(component).toBeTruthy(); }); + + it('should reset form onSubmit and dispatch UpdateProjectType action', () => { + const currentState = { + data: [{ id: '1', name: 'xxx', description: 'xxxx' }], + isLoading: false, + message: '', + projectTypeIdToEdit: '1', + }; + + projectTypeIdToEditMock = store.overrideSelector(projectTypeIdToEdit, currentState.projectTypeIdToEdit); + allProjectTypesMock = store.overrideSelector(allProjectTypes, currentState.data); + getProjectTypeByIdMock = store.overrideSelector(allProjectTypesMock, projectTypeIdToEditMock); + + component.projectTypeToEdit = getProjectTypeByIdMock; + + const projectTypeForm = { + name: 'Develop', + description: 'xxx', + }; + + const projectTypeUpdated = { + id: component.projectTypeToEdit.id, + name: 'Develop', + description: 'xxx', + }; + + spyOn(component.projectTypeForm, 'reset'); + spyOn(store, 'dispatch'); + + component.onSubmit(projectTypeForm); + + expect(component.projectTypeForm.reset).toHaveBeenCalled(); + expect(store.dispatch).toHaveBeenCalledTimes(1); + expect(store.dispatch).toHaveBeenCalledWith(new UpdateProjectType(projectTypeUpdated)); + }); + + it('should reset form onSubmit and dispatch CreateProjectType action', () => { + component.projectTypeToEdit = undefined; + + spyOn(component.projectTypeForm, 'reset'); + spyOn(store, 'dispatch'); + + component.onSubmit(projectType); + + expect(component.projectTypeForm.reset).toHaveBeenCalled(); + expect(store.dispatch).toHaveBeenCalledTimes(1); + expect(store.dispatch).toHaveBeenCalledWith(new CreateProjectType(projectType)); + }); + + it('should get name using projectTypeForm', () => { + spyOn(component.projectTypeForm, 'get'); + // tslint:disable-next-line:no-unused-expression + component.name; + expect(component.projectTypeForm.get).toHaveBeenCalledWith('name'); + }); + + it('should get description using projectTypeForm', () => { + spyOn(component.projectTypeForm, 'get'); + + // tslint:disable-next-line:no-unused-expression + component.description; + + expect(component.projectTypeForm.get).toHaveBeenCalledWith('description'); + }); + + it('should set data in projectTypeForm', () => { + const projectTypeDataForm = { + name: 'Training', + description: 'It is good for learning', + }; + + spyOn(component.projectTypeForm, 'setValue'); + + component.setDataToUpdate(projectType); + expect(component.projectTypeForm.setValue).toHaveBeenCalledTimes(1); + expect(component.projectTypeForm.setValue).toHaveBeenCalledWith(projectTypeDataForm); + }); + + it('should dispatch a ResetProjectTypeToEdit action', () => { + spyOn(store, 'dispatch'); + + component.cancelButton(); + + expect(store.dispatch).toHaveBeenCalledTimes(1); + expect(store.dispatch).toHaveBeenCalledWith(new ResetProjectTypeToEdit()); + }); }); diff --git a/src/app/modules/customer-management/components/projects-type/components/create-project-type/create-project-type.component.ts b/src/app/modules/customer-management/components/projects-type/components/create-project-type/create-project-type.component.ts index 6f1c90d96..41464515d 100644 --- a/src/app/modules/customer-management/components/projects-type/components/create-project-type/create-project-type.component.ts +++ b/src/app/modules/customer-management/components/projects-type/components/create-project-type/create-project-type.component.ts @@ -1,10 +1,68 @@ -import { Component } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; +import { FormBuilder, Validators, FormGroup } from '@angular/forms'; +import { Store, select } from '@ngrx/store'; + +import { ProjectType } from '../../../../../shared/models'; +import { ProjectTypeState } from '../../store'; +import { CreateProjectType, ResetProjectTypeToEdit, UpdateProjectType, getProjectTypeById } from '../../store'; @Component({ selector: 'app-create-project-type', templateUrl: './create-project-type.component.html', styleUrls: ['./create-project-type.component.scss'], }) -export class CreateProjectTypeComponent { - constructor() {} +export class CreateProjectTypeComponent implements OnInit { + projectTypeForm: FormGroup; + projectTypeToEdit: ProjectType; + + constructor(private formBuilder: FormBuilder, private store: Store) { + this.projectTypeForm = this.formBuilder.group({ + name: ['', Validators.required], + description: [''], + }); + } + + ngOnInit(): void { + const projectType$ = this.store.pipe(select(getProjectTypeById)); + projectType$.subscribe((projectType) => { + this.projectTypeToEdit = projectType; + this.setDataToUpdate(this.projectTypeToEdit); + }); + } + + get name() { + return this.projectTypeForm.get('name'); + } + + get description() { + return this.projectTypeForm.get('description'); + } + + setDataToUpdate(projectTypeData: ProjectType) { + if (projectTypeData) { + this.projectTypeForm.setValue({ + name: projectTypeData.name, + description: projectTypeData.description, + }); + } + } + + onSubmit(projectTypeData) { + this.projectTypeForm.reset(); + + if (this.projectTypeToEdit) { + const projectType = { + ...projectTypeData, + id: this.projectTypeToEdit.id, + }; + this.store.dispatch(new UpdateProjectType(projectType)); + } else { + this.store.dispatch(new CreateProjectType(projectTypeData)); + this.projectTypeForm.get('description').setValue(''); + } + } + + cancelButton() { + this.store.dispatch(new ResetProjectTypeToEdit()); + } } diff --git a/src/app/modules/customer-management/components/projects-type/components/project-type-list/project-type-list.component.html b/src/app/modules/customer-management/components/projects-type/components/project-type-list/project-type-list.component.html index af23cb4e2..ec28d05d6 100644 --- a/src/app/modules/customer-management/components/projects-type/components/project-type-list/project-type-list.component.html +++ b/src/app/modules/customer-management/components/projects-type/components/project-type-list/project-type-list.component.html @@ -9,13 +9,13 @@ {{ projectType.name }} - - + + diff --git a/src/app/modules/customer-management/components/projects-type/components/project-type-list/project-type-list.component.spec.ts b/src/app/modules/customer-management/components/projects-type/components/project-type-list/project-type-list.component.spec.ts index e0a48170c..f51c0450a 100644 --- a/src/app/modules/customer-management/components/projects-type/components/project-type-list/project-type-list.component.spec.ts +++ b/src/app/modules/customer-management/components/projects-type/components/project-type-list/project-type-list.component.spec.ts @@ -1,16 +1,29 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { ProjectTypeListComponent } from './project-type-list.component'; +import { provideMockStore, MockStore } from '@ngrx/store/testing'; import { NgxPaginationModule } from 'ngx-pagination'; +import { allProjectTypes, ProjectTypeState } from '../../store'; +import { ProjectTypeListComponent } from './project-type-list.component'; + describe('ProjectTypeTableListComponent', () => { let component: ProjectTypeListComponent; let fixture: ComponentFixture; + let store: MockStore; + let mockProjectTypeSelector; + + const state = { + data: [{ id: 'id', name: 'name', description: 'description' }], + isLoading: false, + message: '', + projectTypeIdToEdit: '', + }; beforeEach(async(() => { TestBed.configureTestingModule({ imports: [NgxPaginationModule], declarations: [ProjectTypeListComponent], + providers: [provideMockStore({ initialState: state })], }).compileComponents(); })); @@ -18,9 +31,30 @@ describe('ProjectTypeTableListComponent', () => { fixture = TestBed.createComponent(ProjectTypeListComponent); component = fixture.componentInstance; fixture.detectChanges(); + + store = TestBed.inject(MockStore); + store.setState(state); + mockProjectTypeSelector = store.overrideSelector(allProjectTypes, state.data); + }); + + afterEach(() => { + fixture.destroy(); }); it('component should be created', () => { expect(component).toBeTruthy(); }); + + it('onInit, LoadProjecttypes action is dispatched', () => { + spyOn(store, 'dispatch'); + + component.ngOnInit(); + expect(store.dispatch).toHaveBeenCalled(); + }); + + it('onInit, projectTypes field is populated with data from store', () => { + component.ngOnInit(); + expect(component.projectTypes).toBe(state.data); + }); + }); diff --git a/src/app/modules/customer-management/components/projects-type/components/project-type-list/project-type-list.component.ts b/src/app/modules/customer-management/components/projects-type/components/project-type-list/project-type-list.component.ts index b5c1e1d9d..069b43d4a 100644 --- a/src/app/modules/customer-management/components/projects-type/components/project-type-list/project-type-list.component.ts +++ b/src/app/modules/customer-management/components/projects-type/components/project-type-list/project-type-list.component.ts @@ -1,25 +1,37 @@ -import { Component } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; import { ITEMS_PER_PAGE } from 'src/environments/environment'; +import { Store, select } from '@ngrx/store'; + +import { LoadProjectTypes, DeleteProjectType, SetProjectTypeToEdit, allProjectTypes } from '../../store'; +import { ProjectTypeState } from '../../store'; +import { ProjectType } from '../../../../../shared/models'; @Component({ selector: 'app-project-type-list', templateUrl: './project-type-list.component.html', styleUrls: ['./project-type-list.component.scss'], }) -export class ProjectTypeListComponent { +export class ProjectTypeListComponent implements OnInit { + projectTypes: ProjectType[] = []; + initPage2 = 1; itemsPerPage = ITEMS_PER_PAGE; - projectsType = [ - { - id: '1', - name: 'Training', - }, - { - id: '2', - name: 'On-site', - }, - ]; + constructor(private store: Store) {} + + ngOnInit(): void { + this.store.dispatch(new LoadProjectTypes()); + const projectTypes$ = this.store.pipe(select(allProjectTypes)); + projectTypes$.subscribe((response) => { + this.projectTypes = response; + }); + } + + deleteProjectType(projectTypeId: string) { + this.store.dispatch(new DeleteProjectType(projectTypeId)); + } - constructor() {} + updateProjectType(projectTypeId: string) { + this.store.dispatch(new SetProjectTypeToEdit(projectTypeId)); + } } diff --git a/src/app/modules/customer-management/components/projects-type/services/project-type.service.spec.ts b/src/app/modules/customer-management/components/projects-type/services/project-type.service.spec.ts new file mode 100644 index 000000000..f8f2c4288 --- /dev/null +++ b/src/app/modules/customer-management/components/projects-type/services/project-type.service.spec.ts @@ -0,0 +1,80 @@ +import { TestBed, inject } from '@angular/core/testing'; +import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; + +import { ProjectType } from '../../../../shared/models'; +import { ProjectTypeService } from './project-type.service'; + +describe('Activity Service', () => { + let service: ProjectTypeService; + let httpMock: HttpTestingController; + + const projectTypes: ProjectType[] = [ + { id: '1', name: 'aaa', description: 'xxx' }, + { id: '2', name: 'bbb', description: 'yyy' }, + ]; + + beforeEach(() => { + TestBed.configureTestingModule({ imports: [HttpClientTestingModule] }); + service = TestBed.inject(ProjectTypeService); + httpMock = TestBed.inject(HttpTestingController); + }); + + afterEach(() => { + httpMock.verify(); + }); + + it('services are ready to be used', inject( + [HttpClientTestingModule, ProjectTypeService], + (httpClient: HttpClientTestingModule, projectTypeService: ProjectTypeService) => { + expect(projectTypeService).toBeTruthy(); + expect(httpClient).toBeTruthy(); + } + )); + + it('projectTypes are read using GET from baseUrl', () => { + const projectTypesFoundSize = projectTypes.length; + service.baseUrl = 'foo'; + service.getProjectTypes().subscribe((projecttypesInResponse) => { + expect(projecttypesInResponse.length).toBe(projectTypesFoundSize); + }); + const getProjectTypesRequest = httpMock.expectOne(service.baseUrl); + expect(getProjectTypesRequest.request.method).toBe('GET'); + getProjectTypesRequest.flush(projectTypes); + }); + + it('create ProjectType using POST from baseUrl', () => { + const activity: ProjectType[] = [{ id: '1', name: 'ccc', description: 'xxx' }]; + + service.baseUrl = 'project-type'; + + service.createProjectType(activity).subscribe((response) => { + expect(response.length).toBe(1); + }); + const createProjectTypesRequest = httpMock.expectOne(service.baseUrl); + expect(createProjectTypesRequest.request.method).toBe('POST'); + createProjectTypesRequest.flush(activity); + }); + + it('ProjectTypes are delete using DELETE from baseUrl', () => { + const url = `${service.baseUrl}/1`; + service.deleteProjectType(projectTypes[0].id).subscribe((projectTypesInResponse) => { + expect(projectTypesInResponse.filter((activity) => activity.id !== projectTypes[0].id)).toEqual([projectTypes[1]]); + }); + const getProjectTypesRequest = httpMock.expectOne(url); + expect(getProjectTypesRequest.request.method).toBe('DELETE'); + getProjectTypesRequest.flush(projectTypes); + }); + + it('update activity using PUT from baseUrl', () => { + const projectType = { id: '1', name: 'aaa', description: 'bbb' }; + + service.baseUrl = 'project-type' + '/' + projectType.id; + + service.updateProjectType(projectType).subscribe((response) => { + expect(response.name).toBe('aaa'); + }); + const updateProjectTypeRequest = httpMock.expectOne(`${service.baseUrl}/${projectType.id}`); + expect(updateProjectTypeRequest.request.method).toBe('PUT'); + updateProjectTypeRequest.flush(projectType); + }); +}); diff --git a/src/app/modules/customer-management/components/projects-type/services/project-type.service.ts b/src/app/modules/customer-management/components/projects-type/services/project-type.service.ts new file mode 100644 index 000000000..c12beacbd --- /dev/null +++ b/src/app/modules/customer-management/components/projects-type/services/project-type.service.ts @@ -0,0 +1,33 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; + +import { environment } from '../../../../../../environments/environment'; +import { ProjectType } from '../../../../shared/models'; + +@Injectable({ + providedIn: 'root', +}) +export class ProjectTypeService { + baseUrl = `${environment.timeTrackerApiUrl}/project-types`; + + constructor(private http: HttpClient) {} + + getProjectTypes(): Observable { + return this.http.get(this.baseUrl); + } + + createProjectType(projectTypeData): Observable { + return this.http.post(this.baseUrl, projectTypeData); + } + + deleteProjectType(projectTypeId: string): Observable { + const url = `${this.baseUrl}/${projectTypeId}`; + return this.http.delete(url); + } + + updateProjectType(projectTypeData): Observable { + const url = `${this.baseUrl}/${projectTypeData.id}`; + return this.http.put(url, projectTypeData); + } +} diff --git a/src/app/modules/customer-management/components/projects-type/store/index.ts b/src/app/modules/customer-management/components/projects-type/store/index.ts new file mode 100644 index 000000000..65865b2ee --- /dev/null +++ b/src/app/modules/customer-management/components/projects-type/store/index.ts @@ -0,0 +1,3 @@ +export * from './project-type.actions'; +export * from './project-type.reducers'; +export * from './project-type.selectors'; diff --git a/src/app/modules/customer-management/components/projects-type/store/project-type.actions.spec.ts b/src/app/modules/customer-management/components/projects-type/store/project-type.actions.spec.ts new file mode 100644 index 000000000..14310a6b8 --- /dev/null +++ b/src/app/modules/customer-management/components/projects-type/store/project-type.actions.spec.ts @@ -0,0 +1,51 @@ +import * as actions from './project-type.actions'; + +describe('LoadProjectTypesSuccess', () => { + it('LoadProjectTypesSuccess type is ProjectTypeActionTypes.LOAD_PROJECT_TYPES_SUCCESS', () => { + const loadProjectTypesSuccess = new actions.LoadProjectTypesSuccess([]); + expect(loadProjectTypesSuccess.type).toEqual(actions.ProjectTypeActionTypes.LOAD_PROJECT_TYPES_SUCCESS); + }); + + it('LoadProjectTypesFail type is ProjectTypeActionTypes.LOAD_PROJECT_TYPES_FAIL', () => { + const loadProjectTypesFail = new actions.LoadProjectTypesFail('error'); + expect(loadProjectTypesFail.type).toEqual(actions.ProjectTypeActionTypes.LOAD_PROJECT_TYPES_FAIL); + }); + + it('CreateProjectTypeSuccess type is ProjectTypeActionTypes.CREATE_PROJECT_TYPE_SUCCESS', () => { + const createProjectTypeSuccess = new actions.CreateProjectTypeSuccess({ + id: '1', + name: 'Training', + description: 'It is good for learning', + }); + expect(createProjectTypeSuccess.type).toEqual(actions.ProjectTypeActionTypes.CREATE_PROJECT_TYPE_SUCCESS); + }); + + it('CreateProjectTypeFail type is ProjectTypeActionTypes.CREATE_PROJECT_TYPE_FAIL', () => { + const createProjectTypeFail = new actions.CreateProjectTypeFail('error'); + expect(createProjectTypeFail.type).toEqual(actions.ProjectTypeActionTypes.CREATE_PROJECT_TYPE_FAIL); + }); + + it('UpdateProjectTypeSuccess type is ProjectTypeActionTypes.UPDATE_PROJECT_TYPE_SUCCESS', () => { + const updateProjectTypeSuccess = new actions.UpdateProjectTypeSuccess({ + id: '1', + name: 'Training', + description: 'test description', + }); + expect(updateProjectTypeSuccess.type).toEqual(actions.ProjectTypeActionTypes.UPDATE_PROJECT_TYPE_SUCCESS); + }); + + it('UpdateProjectTypeFail type is ProjectTypeActionTypes.UPDATE_PROJECT_TYPE_FAIL', () => { + const updateProjectTypeFail = new actions.UpdateProjectTypeFail('error'); + expect(updateProjectTypeFail.type).toEqual(actions.ProjectTypeActionTypes.UPDATE_PROJECT_TYPE_FAIL); + }); + + it('SetProjectTypeToEdit type is ProjectTypeActionTypes.SET_PROJECT_TYPE_ID_TO_EDIT', () => { + const setProjectTypeToEdit = new actions.SetProjectTypeToEdit('123'); + expect(setProjectTypeToEdit.type).toEqual(actions.ProjectTypeActionTypes.SET_PROJECT_TYPE_ID_TO_EDIT); + }); + + it('ResetProjectTypeToEdit type is ProjectTypeActionTypes.RESET_PROJECT_TYPE_ID_TO_EDIT', () => { + const resetProjectTypeToEdit = new actions.ResetProjectTypeToEdit(); + expect(resetProjectTypeToEdit.type).toEqual(actions.ProjectTypeActionTypes.RESET_PROJECT_TYPE_ID_TO_EDIT); + }); +}); diff --git a/src/app/modules/customer-management/components/projects-type/store/project-type.actions.ts b/src/app/modules/customer-management/components/projects-type/store/project-type.actions.ts new file mode 100644 index 000000000..3ed8b63ad --- /dev/null +++ b/src/app/modules/customer-management/components/projects-type/store/project-type.actions.ts @@ -0,0 +1,121 @@ +import { Action } from '@ngrx/store'; + +import { ProjectType } from '../../../../shared/models'; + +export enum ProjectTypeActionTypes { + LOAD_PROJECT_TYPES = '[ProjectType] LOAD_PROJECT_TYPES', + LOAD_PROJECT_TYPES_SUCCESS = '[ProjectType] LOAD_PROJECT_TYPES_SUCCESS', + LOAD_PROJECT_TYPES_FAIL = '[ProjectType] LOAD_PROJECT_TYPES_FAIL', + CREATE_PROJECT_TYPE = '[ProjectType] CREATE_PROJECT_TYPE', + CREATE_PROJECT_TYPE_SUCCESS = '[ProjectType] CREATE_PROJECT_TYPE_SUCCESS', + CREATE_PROJECT_TYPE_FAIL = '[ProjectType] CREATE_PROJECT_TYPE_FAIL', + DELETE_PROJECT_TYPE = '[ProjectType] DELETE_PROJECT_TYPE', + DELETE_PROJECT_TYPE_SUCCESS = '[ProjectType] DELETE_PROJECT_TYPE_SUCESS', + DELETE_PROJECT_TYPE_FAIL = '[ProjectType] DELETE_PROJECT_TYPE_FAIL', + UPDATE_PROJECT_TYPE = '[ProjectType] UPDATE_PROJECT_TYPE', + UPDATE_PROJECT_TYPE_SUCCESS = '[ProjectType] UPDATE_PROJECT_TYPE_SUCCESS', + UPDATE_PROJECT_TYPE_FAIL = '[ProjectType] UPDATE_PROJECT_TYPE_FAIL', + SET_PROJECT_TYPE_ID_TO_EDIT = '[ProjectType] SET_PROJECT_TYPE_ID_TO_EDIT', + RESET_PROJECT_TYPE_ID_TO_EDIT = '[ProjectType] RESET_PROJECT_TYPE_ID_TO_EDIT', + DEFAULT_PROJECT_TYPE = '[ProjectType] DEFAULT_PROJECT_TYPE', +} + +export class LoadProjectTypes implements Action { + public readonly type = ProjectTypeActionTypes.LOAD_PROJECT_TYPES; +} + +export class LoadProjectTypesSuccess implements Action { + public readonly type = ProjectTypeActionTypes.LOAD_PROJECT_TYPES_SUCCESS; + + constructor(public payload: ProjectType[]) {} +} + +export class LoadProjectTypesFail implements Action { + public readonly type = ProjectTypeActionTypes.LOAD_PROJECT_TYPES_FAIL; + + constructor(public error: string) {} +} + +export class CreateProjectType implements Action { + public readonly type = ProjectTypeActionTypes.CREATE_PROJECT_TYPE; + + constructor(public payload: ProjectType) {} +} + +export class CreateProjectTypeSuccess implements Action { + public readonly type = ProjectTypeActionTypes.CREATE_PROJECT_TYPE_SUCCESS; + + constructor(public payload: ProjectType) {} +} + +export class CreateProjectTypeFail implements Action { + public readonly type = ProjectTypeActionTypes.CREATE_PROJECT_TYPE_FAIL; + + constructor(public error: string) {} +} + +export class DeleteProjectType implements Action { + public readonly type = ProjectTypeActionTypes.DELETE_PROJECT_TYPE; + + constructor(public projectTypeId: string) {} +} + +export class DeleteProjectTypeSuccess implements Action { + public readonly type = ProjectTypeActionTypes.DELETE_PROJECT_TYPE_SUCCESS; + + constructor(public projectTypeId: string) {} +} + +export class DeleteProjectTypeFail implements Action { + public readonly type = ProjectTypeActionTypes.DELETE_PROJECT_TYPE_FAIL; + + constructor(public error: string) {} +} +export class UpdateProjectType implements Action { + public readonly type = ProjectTypeActionTypes.UPDATE_PROJECT_TYPE; + + constructor(public payload: ProjectType) {} +} + +export class UpdateProjectTypeSuccess implements Action { + public readonly type = ProjectTypeActionTypes.UPDATE_PROJECT_TYPE_SUCCESS; + + constructor(public payload: ProjectType) {} +} + +export class UpdateProjectTypeFail implements Action { + public readonly type = ProjectTypeActionTypes.UPDATE_PROJECT_TYPE_FAIL; + + constructor(public error: string) {} +} + +export class SetProjectTypeToEdit implements Action { + public readonly type = ProjectTypeActionTypes.SET_PROJECT_TYPE_ID_TO_EDIT; + + constructor(public payload: string) {} +} + +export class ResetProjectTypeToEdit implements Action { + public readonly type = ProjectTypeActionTypes.RESET_PROJECT_TYPE_ID_TO_EDIT; +} + +export class DefaultProjectTypes implements Action { + public readonly type = ProjectTypeActionTypes.DEFAULT_PROJECT_TYPE; +} + +export type ProjectTypeActions = + | LoadProjectTypes + | LoadProjectTypesSuccess + | LoadProjectTypesFail + | CreateProjectType + | CreateProjectTypeSuccess + | CreateProjectTypeFail + | DeleteProjectType + | DeleteProjectTypeSuccess + | DeleteProjectTypeFail + | UpdateProjectType + | UpdateProjectTypeSuccess + | UpdateProjectTypeFail + | SetProjectTypeToEdit + | ResetProjectTypeToEdit + | DefaultProjectTypes; diff --git a/src/app/modules/customer-management/components/projects-type/store/project-type.effects.ts b/src/app/modules/customer-management/components/projects-type/store/project-type.effects.ts new file mode 100644 index 000000000..9e8a0063d --- /dev/null +++ b/src/app/modules/customer-management/components/projects-type/store/project-type.effects.ts @@ -0,0 +1,69 @@ +import { Injectable } from '@angular/core'; +import { Actions, Effect, ofType } from '@ngrx/effects'; +import { Action } from '@ngrx/store'; +import { Observable, of } from 'rxjs'; +import { catchError, map, mergeMap } from 'rxjs/operators'; + +import * as actions from './project-type.actions'; +import { ProjectType } from '../../../../shared/models'; +import { ProjectTypeService } from '../services/project-type.service'; + +@Injectable() +export class ProjectTypeEffects { + constructor(private actions$: Actions, private projectTypeService: ProjectTypeService) {} + + @Effect() + getProjectTypes$: Observable = this.actions$.pipe( + ofType(actions.ProjectTypeActionTypes.LOAD_PROJECT_TYPES), + mergeMap(() => + this.projectTypeService.getProjectTypes().pipe( + map((projectTypes: ProjectType[]) => { + return new actions.LoadProjectTypesSuccess(projectTypes); + }), + catchError((error) => of(new actions.LoadProjectTypesFail(error))) + ) + ) + ); + + @Effect() + createProjectType$: Observable = this.actions$.pipe( + ofType(actions.ProjectTypeActionTypes.CREATE_PROJECT_TYPE), + map((action: actions.CreateProjectType) => action.payload), + mergeMap((projectType) => + this.projectTypeService.createProjectType(projectType).pipe( + map((projectTypeData) => { + return new actions.CreateProjectTypeSuccess(projectTypeData); + }), + catchError((error) => of(new actions.CreateProjectTypeFail(error))) + ) + ) + ); + + @Effect() + deleteProjectType$: Observable = this.actions$.pipe( + ofType(actions.ProjectTypeActionTypes.DELETE_PROJECT_TYPE), + map((action: actions.DeleteProjectType) => action.projectTypeId), + mergeMap((protectTypeId) => + this.projectTypeService.deleteProjectType(protectTypeId).pipe( + map(() => { + return new actions.DeleteProjectTypeSuccess(protectTypeId); + }), + catchError((error) => of(new actions.DeleteProjectTypeFail(error))) + ) + ) + ); + + @Effect() + updateProjectType$: Observable = this.actions$.pipe( + ofType(actions.ProjectTypeActionTypes.UPDATE_PROJECT_TYPE), + map((action: actions.UpdateProjectType) => action.payload), + mergeMap((projectType) => + this.projectTypeService.updateProjectType(projectType).pipe( + map((projectTypeData) => { + return new actions.UpdateProjectTypeSuccess(projectTypeData); + }), + catchError((error) => of(new actions.UpdateProjectTypeFail(error))) + ) + ) + ); +} diff --git a/src/app/modules/customer-management/components/projects-type/store/project-type.reducers.spec.ts b/src/app/modules/customer-management/components/projects-type/store/project-type.reducers.spec.ts new file mode 100644 index 000000000..b7d9a78ea --- /dev/null +++ b/src/app/modules/customer-management/components/projects-type/store/project-type.reducers.spec.ts @@ -0,0 +1,137 @@ +import { ProjectType } from '../../../../shared/models'; +import * as actions from './project-type.actions'; +import { projectTypeReducer, ProjectTypeState } from './project-type.reducers'; + +describe('projectTypeReducer', () => { + const initialState: ProjectTypeState = { data: [], isLoading: false, message: '', projectTypeIdToEdit: '' }; + const projectType: ProjectType = { id: '1', name: 'Training', description: 'It is good for learning' }; + + it('on Default, ', () => { + const action = new actions.DefaultProjectTypes(); + const state = projectTypeReducer(initialState, action); + expect(state.data).toEqual(initialState.data); + }); + + it('on LoadProjectTypes, isLoading is true', () => { + const action = new actions.LoadProjectTypes(); + + const state = projectTypeReducer(initialState, action); + + expect(state.isLoading).toEqual(true); + }); + + it('on LoadProjectTypesSuccess, projectTypesFound are saved in the store', () => { + const projectTypesFound: ProjectType[] = [{ id: '', name: '', description: '' }]; + const action = new actions.LoadProjectTypesSuccess(projectTypesFound); + + const state = projectTypeReducer(initialState, action); + + expect(state.data).toEqual(projectTypesFound); + }); + + it('on LoadProjectTypesFail, message equal to Something went wrong fetching projectType!', () => { + const action = new actions.LoadProjectTypesFail('error'); + + const state = projectTypeReducer(initialState, action); + + expect(state.message).toEqual('Something went wrong fetching projectType!'); + }); + + it('on CreateProjectType, isLoading is true', () => { + const action = new actions.CreateProjectType(projectType); + + const state = projectTypeReducer(initialState, action); + + expect(state.isLoading).toEqual(true); + }); + + it('on CreateProjectTypeSuccess, activitiesFound are saved in the store', () => { + const action = new actions.CreateProjectTypeSuccess(projectType); + + const state = projectTypeReducer(initialState, action); + + expect(state.data).toEqual([projectType]); + expect(state.isLoading).toEqual(false); + }); + + it('on CreateProjectTypeFail, message equal to Something went wrong creating projectType!', () => { + const action = new actions.CreateProjectTypeFail('error'); + + const state = projectTypeReducer(initialState, action); + + expect(state.message).toEqual('Something went wrong creating projectType!'); + expect(state.isLoading).toEqual(false); + }); + + it('on DeleteProjectType, isLoading is true', () => { + const ProjectTypeToDeleteId = '1'; + const action = new actions.DeleteProjectType(ProjectTypeToDeleteId); + + const state = projectTypeReducer(initialState, action); + expect(state.isLoading).toEqual(true); + }); + + it('on DeleteProjectTypeSuccess, message equal to ProjectType removed successfully!', () => { + const currentState: ProjectTypeState = { data: [projectType], isLoading: false, message: '', projectTypeIdToEdit: '' }; + const ProjectTypeToDeleteId = '1'; + const action = new actions.DeleteProjectTypeSuccess(ProjectTypeToDeleteId); + + const state = projectTypeReducer(currentState, action); + expect(state.data).toEqual([]); + expect(state.message).toEqual('ProjectType removed successfully!'); + }); + + it('on DeleteProjectTypeFail, message equal to Something went wrong deleting ProjectType!', () => { + const ProjectTypeToDeleteId = '1'; + const action = new actions.DeleteProjectTypeFail(ProjectTypeToDeleteId); + + const state = projectTypeReducer(initialState, action); + expect(state.isLoading).toEqual(false); + expect(state.message).toEqual('Something went wrong deleting projectType!'); + }); + + it('on UpdateProjectType, isLoading is true', () => { + const action = new actions.UpdateProjectType(projectType); + + const state = projectTypeReducer(initialState, action); + + expect(state.isLoading).toEqual(true); + }); + + it('on UpdateProjectTypeSuccess, activitiesFound are saved in the store', () => { + const currentState: ProjectTypeState = { data: [projectType], isLoading: false, message: '', projectTypeIdToEdit: '1' }; + const ProjectTypeEdited: ProjectType = { id: '1', name: 'Test', description: 'edit test' }; + + const action = new actions.UpdateProjectTypeSuccess(ProjectTypeEdited); + + const state = projectTypeReducer(currentState, action); + + expect(state.data).toEqual([ProjectTypeEdited]); + expect(state.isLoading).toEqual(false); + }); + + it('on UpdateProjectTypeFail, message equal to Something went wrong creating projectType!', () => { + const action = new actions.UpdateProjectTypeFail('error'); + + const state = projectTypeReducer(initialState, action); + + expect(state.message).toEqual('Something went wrong updating projectType!'); + expect(state.isLoading).toEqual(false); + }); + + it('on SetProjectTypeToEdit, should save the ProjectTypeId to edit', () => { + const action = new actions.SetProjectTypeToEdit(projectType.id); + + const state = projectTypeReducer(initialState, action); + + expect(state.projectTypeIdToEdit).toEqual('1'); + }); + + it('on ResetProjectTypeToEdit, should clean the ProjectTypeIdToEdit variable', () => { + const action = new actions.ResetProjectTypeToEdit(); + + const state = projectTypeReducer(initialState, action); + + expect(state.projectTypeIdToEdit).toEqual(''); + }); +}); diff --git a/src/app/modules/customer-management/components/projects-type/store/project-type.reducers.ts b/src/app/modules/customer-management/components/projects-type/store/project-type.reducers.ts new file mode 100644 index 000000000..5a1b5fb11 --- /dev/null +++ b/src/app/modules/customer-management/components/projects-type/store/project-type.reducers.ts @@ -0,0 +1,144 @@ +import { ProjectTypeActions, ProjectTypeActionTypes } from './project-type.actions'; +import { ProjectType } from '../../../../shared/models'; + +export interface ProjectTypeState { + data: ProjectType[]; + isLoading: boolean; + message: string; + projectTypeIdToEdit: string; +} + +export const initialState: ProjectTypeState = { + data: [], + isLoading: false, + message: '', + projectTypeIdToEdit: '', +}; + +export const projectTypeReducer = (state: ProjectTypeState = initialState, action: ProjectTypeActions) => { + const projectTypeList = [...state.data]; + switch (action.type) { + case ProjectTypeActionTypes.LOAD_PROJECT_TYPES: { + return { + ...state, + isLoading: true, + }; + } + + case ProjectTypeActionTypes.LOAD_PROJECT_TYPES_SUCCESS: { + return { + ...state, + data: action.payload, + isLoading: false, + message: 'Data fetch successfully!', + }; + } + case ProjectTypeActionTypes.LOAD_PROJECT_TYPES_FAIL: { + return { + data: [], + isLoading: false, + message: 'Something went wrong fetching projectType!', + projectTypeIdToEdit: '', + }; + } + + case ProjectTypeActionTypes.CREATE_PROJECT_TYPE: { + return { + ...state, + isLoading: true, + }; + } + + case ProjectTypeActionTypes.CREATE_PROJECT_TYPE_SUCCESS: { + return { + ...state, + data: [...state.data, action.payload], + isLoading: false, + message: 'Data created successfully!', + }; + } + + case ProjectTypeActionTypes.CREATE_PROJECT_TYPE_FAIL: { + return { + data: [], + isLoading: false, + message: 'Something went wrong creating projectType!', + projectTypeIdToEdit: '', + }; + } + + case ProjectTypeActionTypes.DELETE_PROJECT_TYPE: { + return { + ...state, + isLoading: true, + }; + } + + case ProjectTypeActionTypes.DELETE_PROJECT_TYPE_SUCCESS: { + const activites = state.data.filter((projectType) => projectType.id !== action.projectTypeId); + return { + ...state, + data: activites, + isLoading: false, + message: 'ProjectType removed successfully!', + }; + } + + case ProjectTypeActionTypes.DELETE_PROJECT_TYPE_FAIL: { + return { + data: [], + isLoading: false, + message: 'Something went wrong deleting projectType!', + projectTypeIdToEdit: '', + }; + } + + case ProjectTypeActionTypes.UPDATE_PROJECT_TYPE: { + return { + ...state, + isLoading: true, + }; + } + + case ProjectTypeActionTypes.UPDATE_PROJECT_TYPE_SUCCESS: { + const index = projectTypeList.findIndex((projectType) => projectType.id === action.payload.id); + projectTypeList[index] = action.payload; + + return { + ...state, + data: projectTypeList, + isLoading: false, + message: 'Data updated successfully!', + projectTypeIdToEdit: '', + }; + } + + case ProjectTypeActionTypes.UPDATE_PROJECT_TYPE_FAIL: { + return { + data: [], + isLoading: false, + message: 'Something went wrong updating projectType!', + projectTypeIdToEdit: '', + }; + } + + case ProjectTypeActionTypes.SET_PROJECT_TYPE_ID_TO_EDIT: { + return { + ...state, + projectTypeIdToEdit: action.payload, + message: 'Set projectTypeIdToEdit property', + }; + } + + case ProjectTypeActionTypes.RESET_PROJECT_TYPE_ID_TO_EDIT: { + return { + ...state, + projectTypeIdToEdit: '', + message: 'Reset projectTypeIdToEdit property', + }; + } + + default: + return state; + } +}; diff --git a/src/app/modules/customer-management/components/projects-type/store/project-type.selectors.ts b/src/app/modules/customer-management/components/projects-type/store/project-type.selectors.ts new file mode 100644 index 000000000..389f5d2d2 --- /dev/null +++ b/src/app/modules/customer-management/components/projects-type/store/project-type.selectors.ts @@ -0,0 +1,21 @@ +import { createFeatureSelector, createSelector } from '@ngrx/store'; + +import { ProjectTypeState } from './project-type.reducers'; + +const getProjectTypeState = createFeatureSelector('projectType'); + +export const allProjectTypes = createSelector(getProjectTypeState, (state: ProjectTypeState) => { + return state.data; +}); + +export const projectTypeIdToEdit = createSelector(getProjectTypeState, (state: ProjectTypeState) => { + return state.projectTypeIdToEdit; +}); + +export const getProjectTypeById = createSelector(allProjectTypes, projectTypeIdToEdit, (projectType, projectTypeId) => { + if (projectType && projectTypeId) { + return projectType.find((activity) => { + return activity.id === projectTypeId; + }); + } +}); diff --git a/src/app/modules/shared/models/index.ts b/src/app/modules/shared/models/index.ts index 71110c420..671856d4b 100644 --- a/src/app/modules/shared/models/index.ts +++ b/src/app/modules/shared/models/index.ts @@ -3,3 +3,5 @@ export * from './entry.model'; export * from './project.model'; export * from './technology.model'; export * from './customer.model'; +export * from './project-type.model'; + diff --git a/src/app/modules/shared/models/project-type.model.ts b/src/app/modules/shared/models/project-type.model.ts new file mode 100644 index 000000000..8ac53a01b --- /dev/null +++ b/src/app/modules/shared/models/project-type.model.ts @@ -0,0 +1,5 @@ +export interface ProjectType { + id: string; + name: string; + description?: string; +} diff --git a/src/app/modules/shared/pipes/filter-project/filter-project.pipe.spec.ts b/src/app/modules/shared/pipes/filter-project/filter-project.pipe.spec.ts index b884a8c9b..d00d98669 100644 --- a/src/app/modules/shared/pipes/filter-project/filter-project.pipe.spec.ts +++ b/src/app/modules/shared/pipes/filter-project/filter-project.pipe.spec.ts @@ -5,7 +5,7 @@ describe('FilterProjectPipe', () => { const projects: Project[] = [ { id: '1', - name: 'app 1', + name: 'App 1', description: 'It is a good app', }, { @@ -15,7 +15,7 @@ describe('FilterProjectPipe', () => { }, { id: '3', - name: 'app 3', + name: 'App 3', description: 'It is a good app', }, ]; @@ -31,4 +31,11 @@ describe('FilterProjectPipe', () => { const result = new FilterProjectPipe().transform(restultProjects, searchProject); expect(result.length).toEqual(1); }); + + it('test method of pipe #transform without element', () => { + const restultProjects = projects; + const searchProject = 'app 4'; + const result = new FilterProjectPipe().transform(restultProjects, searchProject); + expect(result.length).toEqual(0); + }); }); diff --git a/src/app/modules/shared/pipes/filter-project/filter-project.pipe.ts b/src/app/modules/shared/pipes/filter-project/filter-project.pipe.ts index 51aded839..eed540765 100644 --- a/src/app/modules/shared/pipes/filter-project/filter-project.pipe.ts +++ b/src/app/modules/shared/pipes/filter-project/filter-project.pipe.ts @@ -5,7 +5,7 @@ import { Project } from 'src/app/modules/shared/models/project.model'; name: 'filterProject', }) export class FilterProjectPipe implements PipeTransform { - transform(value: Project[] = [], arg: string): Project[] { + transform(value: Project[], arg: string): Project[] { const restultProjects = []; for (const project of value) { if (project.name.toLowerCase().indexOf(arg.toLowerCase()) > -1) { diff --git a/src/app/reducers/index.ts b/src/app/reducers/index.ts index 611dfc3d9..080e05509 100644 --- a/src/app/reducers/index.ts +++ b/src/app/reducers/index.ts @@ -3,6 +3,7 @@ import { projectReducer } from '../modules/project-management/store/project.redu import { activityManagementReducer } from '../modules/activities-management/store/activity-management.reducers'; import { technologyReducer } from '../modules/shared/store/technology.reducers'; import { customerManagementReducer } from '../modules/customer-management/store/customer-management.reducers'; +import { projectTypeReducer } from '../modules/customer-management/components/projects-type/store/project-type.reducers'; import { environment } from '../../environments/environment'; export interface State { @@ -10,6 +11,7 @@ export interface State { activities; technologies; customers; + projectType; } export const reducers: ActionReducerMap = { @@ -17,6 +19,7 @@ export const reducers: ActionReducerMap = { activities: activityManagementReducer, customers: customerManagementReducer, technologies: technologyReducer, + projectType: projectTypeReducer, }; export const metaReducers: MetaReducer[] = !environment.production ? [] : []; diff --git a/src/environments/keys.example.json b/src/environments/keys.example.json deleted file mode 100644 index c65a76e7f..000000000 --- a/src/environments/keys.example.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "authority": "XXXXX", - "client_id": "XXXXX", - "scopes": ["XXXXX"] - } - \ No newline at end of file diff --git a/src/environments/keys.ts b/src/environments/keys.example.ts similarity index 100% rename from src/environments/keys.ts rename to src/environments/keys.example.ts