Skip to content

Commit 891490a

Browse files
committed
fix: #91 manage-projects
1 parent 4ca6c8e commit 891490a

36 files changed

+712
-165
lines changed

src/app/app.module.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,11 @@ import { ActivitiesManagementComponent } from './modules/activities-management/p
2828
import { ActivityListComponent } from './modules/activities-management/components/activity-list/activity-list.component';
2929
import { CreateActivityComponent } from './modules/activities-management/components/create-activity/create-activity.component';
3030
import { FilterProjectPipe } from './modules/shared/pipes/filter-project/filter-project.pipe';
31-
import { SearchProjectComponent } from './modules/shared/components/search-project/search-project.component';
31+
import { SearchComponent } from './modules/shared/components/search/search.component';
3232
import { HomeComponent } from './modules/home/home.component';
3333
import { LoginComponent } from './modules/login/login.component';
3434
import { ActivityEffects } from './modules/activities-management/store/activity-management.effects';
35-
import { ProjectEffects } from './modules/project-management/store/project.effects';
35+
import { ProjectEffects } from './modules/customer-management/components/projects/components/store/project.effects';
3636
import { TechnologyEffects } from './modules/shared/store/technology.effects';
3737
import { ProjectTypeEffects } from './modules/customer-management/components/projects-type/store/project-type.effects';
3838
import { reducers, metaReducers } from './reducers';
@@ -76,7 +76,7 @@ import { InjectTokenInterceptor } from './modules/shared/interceptors/inject.tok
7676
HomeComponent,
7777
LoginComponent,
7878
FilterProjectPipe,
79-
SearchProjectComponent,
79+
SearchComponent,
8080
CustomerComponent,
8181
CustomerListComponent,
8282
ManagementCustomerProjectsComponent,
@@ -105,11 +105,13 @@ import { InjectTokenInterceptor } from './modules/shared/interceptors/inject.tok
105105
: [],
106106
EffectsModule.forRoot([ProjectEffects, ActivityEffects, CustomerEffects, TechnologyEffects, ProjectTypeEffects]),
107107
],
108-
providers: [{
109-
provide: HTTP_INTERCEPTORS,
110-
useClass: InjectTokenInterceptor,
111-
multi: true,
112-
}],
108+
providers: [
109+
{
110+
provide: HTTP_INTERCEPTORS,
111+
useClass: InjectTokenInterceptor,
112+
multi: true,
113+
},
114+
],
113115
bootstrap: [AppComponent],
114116
})
115117
export class AppModule {}

src/app/modules/activities-management/services/activity.service.spec.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,9 @@ describe('Activity Service', () => {
6060
service.deleteActivity(activities[0].id).subscribe((activitiesInResponse) => {
6161
expect(activitiesInResponse.filter((activity) => activity.id !== activities[0].id)).toEqual([activities[1]]);
6262
});
63-
const getActivitiesRequest = httpMock.expectOne(url);
64-
expect(getActivitiesRequest.request.method).toBe('DELETE');
65-
getActivitiesRequest.flush(activities);
63+
const deleteActivitiesRequest = httpMock.expectOne(url);
64+
expect(deleteActivitiesRequest.request.method).toBe('DELETE');
65+
deleteActivitiesRequest.flush(activities);
6666
});
6767

6868
it('update activity using PUT from baseUrl', () => {

src/app/modules/customer-management/components/management-customer-projects/management-customer-projects.component.html

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,10 @@
4949
></app-create-customer>
5050
</div>
5151
<div class="tab-pane fade mt-3" id="projects" role="tabpanel" aria-labelledby="projects-tab">
52-
<app-create-project></app-create-project>
52+
<div class="container mb-1">
53+
<app-create-project></app-create-project>
54+
<app-project-list></app-project-list>
55+
</div>
5356
</div>
5457
<div class="tab-pane fade mt-3" id="projectsType" role="tabpanel" aria-labelledby="projects-type-tab">
5558
<div class="container">

src/app/modules/customer-management/components/projects-type/store/project-type.selectors.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,14 @@ export const projectTypeIdToEdit = createSelector(getProjectTypeState, (state: P
1212
return state.projectTypeIdToEdit;
1313
});
1414

15-
export const getProjectTypeById = createSelector(allProjectTypes, projectTypeIdToEdit, (projectType, projectTypeId) => {
16-
if (projectType && projectTypeId) {
17-
return projectType.find((activity) => {
18-
return activity.id === projectTypeId;
19-
});
15+
export const getProjectTypeById = createSelector(
16+
allProjectTypes,
17+
projectTypeIdToEdit,
18+
(projectTypes, projectTypeId) => {
19+
if (projectTypes && projectTypeId) {
20+
return projectTypes.find((projectType) => {
21+
return projectType.id === projectTypeId;
22+
});
23+
}
2024
}
21-
});
25+
);
Lines changed: 34 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,36 @@
1-
<div class="container mb-1">
2-
<form style="width: 600px;">
3-
<div class="form-group">
4-
<input type="text" class="form-control form-control-sm" id="" aria-describedby="" placeholder="Project name" />
5-
<textarea
6-
class="form-control form-control-sm mt-2"
7-
id="exampleFormControlTextarea1"
8-
rows="3"
9-
placeholder="Description"
10-
></textarea>
11-
<select class="form-group custom-select custom-select-sm mt-2">
12-
<option selected>Select project type</option>
13-
<option value="1">a..</option>
14-
<option value="2">b..</option>
15-
</select>
16-
<button type="submit" class="btn btn-sm btn-primary">Save</button>
17-
<button type="submit" class="btn btn-sm btn-secondary mb-2 ml-2 mt-2">Cancel</button>
18-
</div>
19-
</form>
20-
<hr />
21-
<div class="row text-right">
22-
<hr />
23-
<div class="col-5 text-right">
24-
<app-search-project></app-search-project>
1+
<form style="width: 600px;" [formGroup]="projectForm" (ngSubmit)="onSubmit(projectForm.value)">
2+
<div class="form-group">
3+
<input
4+
type="text"
5+
class="form-control form-control-sm"
6+
placeholder="Project name"
7+
formControlName="name"
8+
[class.is-invalid]="name.invalid && name.touched"
9+
/>
10+
<div class="text-danger" *ngIf="(name.dirty || name.touched) && name.invalid && name.errors.required">
11+
Project name is required.
2512
</div>
2613
</div>
27-
<app-project-list></app-project-list>
28-
</div>
14+
<textarea
15+
class="form-control form-control-sm mt-2"
16+
rows="3"
17+
formControlName="description"
18+
placeholder="Description"
19+
></textarea>
20+
<div class="form-group">
21+
<select class="custom-select custom-select-sm mt-2" formControlName="project_type_id">
22+
<option [value]="" disabled selected>Select project type</option>
23+
<option *ngFor="let type of projectsTypes" [value]="type.id">{{ type.name }}</option>
24+
</select>
25+
</div>
26+
<button type="submit" [disabled]="!projectForm.valid" class="btn btn-sm btn-primary">Save</button>
27+
<button
28+
type="reset"
29+
[hidden]="!projectToEdit"
30+
(click)="cancelButton()"
31+
class="btn btn-sm btn-secondary mb-2 ml-2 mt-2"
32+
>
33+
Cancel
34+
</button>
35+
</form>
36+
<hr />
Lines changed: 128 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,151 @@
11
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2-
2+
import { MockStore, provideMockStore } from '@ngrx/store/testing';
3+
import { FormBuilder } from '@angular/forms';
4+
import { Subscription } from 'rxjs';
35
import { CreateProjectComponent } from './create-project.component';
6+
import { ProjectState } from '../store/project.reducer';
7+
import { Project } from 'src/app/modules/shared/models';
8+
import { getProjectToEdit } from '../store/project.selectors';
9+
import { UpdateProject, CreateProject, ResetProjectToEdit } from '../store/project.actions';
410

511
describe('InputProjectComponent', () => {
612
let component: CreateProjectComponent;
713
let fixture: ComponentFixture<CreateProjectComponent>;
14+
let store: MockStore<ProjectState>;
15+
let getProjectToEditMock;
16+
17+
const state = {
18+
projectList: [{ id: '', name: '', project_type_id: '' }],
19+
isLoading: false,
20+
message: '',
21+
projectToEdit: undefined,
22+
};
23+
24+
const project: Project = {
25+
id: '1',
26+
name: 'Test',
27+
description: 'xxx',
28+
project_type_id: '123',
29+
};
30+
31+
const projectForm = {
32+
name: 'Test',
33+
description: 'xxx',
34+
project_type_id: '123',
35+
};
836

937
beforeEach(async(() => {
1038
TestBed.configureTestingModule({
1139
declarations: [CreateProjectComponent],
40+
providers: [FormBuilder, provideMockStore({ initialState: state })],
1241
}).compileComponents();
1342
}));
1443

1544
beforeEach(() => {
1645
fixture = TestBed.createComponent(CreateProjectComponent);
1746
component = fixture.componentInstance;
1847
fixture.detectChanges();
48+
49+
store = TestBed.inject(MockStore);
50+
store.setState(state);
51+
52+
component.projectToEditSubscription = new Subscription();
53+
component.projectTypesSubscription = new Subscription();
54+
});
55+
56+
afterEach(() => {
57+
fixture.destroy();
1958
});
2059

2160
it('component should be created', () => {
2261
expect(component).toBeTruthy();
2362
});
63+
64+
it('should destroy the subscriptions', () => {
65+
component.projectToEditSubscription = new Subscription();
66+
component.projectTypesSubscription = new Subscription();
67+
const subscription = spyOn(component.projectToEditSubscription, 'unsubscribe');
68+
const projectTypeSubscription = spyOn(component.projectTypesSubscription, 'unsubscribe');
69+
70+
component.ngOnDestroy();
71+
72+
expect(subscription).toHaveBeenCalledTimes(1);
73+
expect(projectTypeSubscription).toHaveBeenCalledTimes(1);
74+
});
75+
76+
it('should reset form #onSubmit and dispatch UpdateProject action', () => {
77+
const currentState = {
78+
data: [project],
79+
isLoading: false,
80+
message: '',
81+
projectToEdit: project,
82+
};
83+
84+
getProjectToEditMock = store.overrideSelector(getProjectToEdit, currentState.projectToEdit);
85+
component.projectToEdit = getProjectToEditMock;
86+
87+
const projectUpdated = {
88+
id: component.projectToEdit.id,
89+
name: 'Test',
90+
description: 'xxx',
91+
project_type_id: '123',
92+
};
93+
94+
component.projectToEditSubscription = new Subscription();
95+
component.projectTypesSubscription = new Subscription();
96+
spyOn(component.projectForm, 'reset');
97+
spyOn(store, 'dispatch');
98+
const subscription = spyOn(component.projectToEditSubscription, 'unsubscribe');
99+
const projectTypeSubscription = spyOn(component.projectTypesSubscription, 'unsubscribe');
100+
101+
component.onSubmit(projectForm);
102+
component.ngOnDestroy();
103+
104+
expect(subscription).toHaveBeenCalledTimes(1);
105+
expect(projectTypeSubscription).toHaveBeenCalledTimes(1);
106+
107+
expect(component.projectForm.reset).toHaveBeenCalled();
108+
expect(store.dispatch).toHaveBeenCalledTimes(1);
109+
expect(store.dispatch).toHaveBeenCalledWith(new UpdateProject(projectUpdated));
110+
});
111+
112+
it('should reset form onSubmit and dispatch CreateProject action', () => {
113+
component.projectToEdit = undefined;
114+
115+
spyOn(component.projectForm, 'reset');
116+
spyOn(store, 'dispatch');
117+
118+
component.onSubmit(project);
119+
120+
expect(component.projectForm.reset).toHaveBeenCalled();
121+
expect(store.dispatch).toHaveBeenCalledTimes(1);
122+
expect(store.dispatch).toHaveBeenCalledWith(new CreateProject(project));
123+
});
124+
125+
it('should set data in projectForm', () => {
126+
component.projectToEditSubscription = new Subscription();
127+
component.projectTypesSubscription = new Subscription();
128+
component.projectToEdit = project;
129+
130+
const subscription = spyOn(component.projectToEditSubscription, 'unsubscribe');
131+
const projectTypeSubscription = spyOn(component.projectTypesSubscription, 'unsubscribe');
132+
spyOn(component.projectForm, 'setValue');
133+
134+
component.setDataToUpdate(project);
135+
component.ngOnDestroy();
136+
137+
expect(subscription).toHaveBeenCalledTimes(1);
138+
expect(projectTypeSubscription).toHaveBeenCalledTimes(1);
139+
expect(component.projectForm.setValue).toHaveBeenCalledTimes(1);
140+
expect(component.projectForm.setValue).toHaveBeenCalledWith(projectForm);
141+
});
142+
143+
it('should dispatch a ResetActivityToEdit action', () => {
144+
spyOn(store, 'dispatch');
145+
146+
component.cancelButton();
147+
148+
expect(store.dispatch).toHaveBeenCalledTimes(1);
149+
expect(store.dispatch).toHaveBeenCalledWith(new ResetProjectToEdit());
150+
});
24151
});
Lines changed: 82 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,89 @@
1-
import { Component } from '@angular/core';
1+
import { Component, OnInit, OnDestroy } from '@angular/core';
2+
import { FormBuilder, Validators } from '@angular/forms';
3+
import { Store, select } from '@ngrx/store';
4+
5+
import { ProjectState } from '../store/project.reducer';
6+
import * as actions from '../store/project.actions';
7+
import { getProjectToEdit } from '../store/project.selectors';
8+
import { Project, ProjectType } from 'src/app/modules/shared/models';
9+
import { allProjectTypes, ProjectTypeState } from '../../../projects-type/store';
10+
import { Subscription } from 'rxjs';
211

312
@Component({
413
selector: 'app-create-project',
514
templateUrl: './create-project.component.html',
615
styleUrls: ['./create-project.component.scss'],
716
})
8-
export class CreateProjectComponent {
9-
constructor() {}
17+
export class CreateProjectComponent implements OnInit, OnDestroy {
18+
projectForm;
19+
projectToEdit: Project;
20+
projectsTypes: ProjectType[] = [];
21+
22+
projectTypesSubscription: Subscription;
23+
projectToEditSubscription: Subscription;
24+
constructor(
25+
private formBuilder: FormBuilder,
26+
private store: Store<ProjectState>,
27+
private projectTypeStore: Store<ProjectTypeState>
28+
) {
29+
this.projectForm = this.formBuilder.group({
30+
name: ['', Validators.required],
31+
description: [''],
32+
project_type_id: [''],
33+
});
34+
}
35+
36+
ngOnInit() {
37+
const projectToEditSubscription = this.store.pipe(select(getProjectToEdit));
38+
projectToEditSubscription.subscribe((project) => {
39+
this.projectToEdit = project;
40+
this.setDataToUpdate(this.projectToEdit);
41+
});
42+
43+
const projectTypesSubscription = this.projectTypeStore.pipe(select(allProjectTypes));
44+
projectTypesSubscription.subscribe((projectsType) => {
45+
this.projectsTypes = projectsType;
46+
});
47+
}
48+
49+
ngOnDestroy() {
50+
this.projectToEditSubscription.unsubscribe();
51+
this.projectTypesSubscription.unsubscribe();
52+
}
53+
54+
onSubmit(formData) {
55+
this.projectForm.reset();
56+
if (this.projectToEdit) {
57+
const projectData = { id: this.projectToEdit.id, ...formData };
58+
this.store.dispatch(new actions.UpdateProject(projectData));
59+
} else {
60+
this.store.dispatch(new actions.CreateProject(formData));
61+
}
62+
}
63+
64+
get name() {
65+
return this.projectForm.get('name');
66+
}
67+
68+
get description() {
69+
return this.projectForm.get('description');
70+
}
71+
72+
get project_type_id() {
73+
return this.projectForm.get('project_type_id');
74+
}
75+
76+
setDataToUpdate(projectData: Project) {
77+
if (projectData) {
78+
this.projectForm.setValue({
79+
name: projectData.name,
80+
description: projectData.description,
81+
project_type_id: projectData.project_type_id,
82+
});
83+
}
84+
}
85+
86+
cancelButton() {
87+
this.store.dispatch(new actions.ResetProjectToEdit());
88+
}
1089
}

0 commit comments

Comments
 (0)