Skip to content

Commit 1c57339

Browse files
committed
#29 Get Technologies
1 parent c21254e commit 1c57339

23 files changed

+448
-49
lines changed

src/app/app.module.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import { HomeComponent } from './modules/home/home.component';
3232
import { LoginComponent } from './modules/login/login.component';
3333
import { ActivityEffects } from './modules/activities-management/store/activity-management.effects';
3434
import { ProjectEffects } from './modules/project-management/store/project.effects';
35+
import { TechnologyEffects } from './modules/shared/store/technology.effects';
3536
import { reducers, metaReducers } from './reducers';
3637
import { environment } from '../environments/environment';
3738
import { CustomerComponent } from './modules/customer-managment/pages/customer.component';
@@ -99,7 +100,7 @@ import { CreateProjectTypeComponent } from './modules/customer-managment/compone
99100
maxAge: 15, // Retains last 15 states
100101
})
101102
: [],
102-
EffectsModule.forRoot([ProjectEffects, ActivityEffects]),
103+
EffectsModule.forRoot([ProjectEffects, ActivityEffects, TechnologyEffects]),
103104
],
104105
providers: [],
105106
bootstrap: [AppComponent],

src/app/modules/shared/components/details-fields/details-fields.component.html

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,18 +36,41 @@
3636
aria-describedby="inputGroup-sizing-sm"
3737
/>
3838
</div>
39+
3940
<div class="input-group input-group-sm mb-3">
4041
<div class="input-group-prepend">
4142
<span class="input-group-text span-width" id="inputGroup-sizing-sm">Technology</span>
4243
</div>
4344
<input
44-
formControlName="technology"
45+
(keypress)="getTechnologies($event.target.value)"
4546
type="text"
4647
class="form-control"
4748
aria-label="Small"
4849
aria-describedby="inputGroup-sizing-sm"
4950
/>
5051
</div>
52+
53+
<app-search-project (changeFilterProject)="filterTechnology = $event"></app-search-project>
54+
<div *ngIf="isLoading">LOADING...</div>
55+
<div *ngIf="technologies" class="d-flex flex-column technology-content">
56+
<div
57+
*ngFor="let technology of technologies.items | filterProject: filterTechnology"
58+
(click)="setTechnology(technology.name)"
59+
class="technology-list"
60+
[ngClass]="{ active: selectedTechnology.includes(technology.name) }"
61+
>
62+
{{ technology.name }}
63+
</div>
64+
</div>
65+
<div class="tags-content d-flex flex-wrap">
66+
<span
67+
*ngFor="let technology of selectedTechnology; let tagIndex = index"
68+
(click)="removeTag(tagIndex)"
69+
class="tag"
70+
>{{ technology }}</span
71+
>
72+
</div>
73+
5174
<div class="form-group">
5275
<label for="NotesTextarea">Notes</label>
5376
<textarea formControlName="comments" class="form-control" id="NotesTextarea" rows="3"></textarea>

src/app/modules/shared/components/details-fields/details-fields.component.scss

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,15 @@
11
@import '../../../../../styles/colors.scss';
2+
3+
@mixin tagTechnology() {
4+
background-color: $modal-button-secondary;
5+
color: #ffffff;
6+
cursor: pointer;
7+
8+
&:hover {
9+
opacity: 0.8;
10+
}
11+
}
12+
213
.span-width {
314
width: 6rem;
415
background-image: $background-pantone;
@@ -18,3 +29,52 @@
1829
background-color: $modal-button-secondary;
1930
color: white;
2031
}
32+
33+
.technology-content {
34+
margin-bottom: 2rem;
35+
max-height: 7.5rem;
36+
overflow-y: auto;
37+
38+
.technology-list {
39+
background-color: #efefef;
40+
cursor: pointer;
41+
font-size: 0.8rem;
42+
margin-bottom: 0.1rem;
43+
padding: 0.2rem 0.5rem;
44+
45+
&:hover {
46+
opacity: 0.7;
47+
}
48+
}
49+
50+
.active {
51+
@include tagTechnology();
52+
}
53+
}
54+
55+
.tags-content {
56+
margin-bottom: 2rem;
57+
58+
span {
59+
@include tagTechnology();
60+
61+
border-radius: 0.2rem;
62+
box-shadow: 2px 2px 5px 0px rgba(0, 0, 0, 0.75);
63+
font-size: 0.8rem;
64+
padding: 0 1rem 0.2rem 1.5rem;
65+
position: relative;
66+
margin: 0 0.5rem 0.5rem 0;
67+
68+
&::before {
69+
background: #fff;
70+
border-radius: 1rem;
71+
box-shadow: inset 0 1px rgba(0, 0, 0, 0.6);
72+
content: '';
73+
height: 0.4rem;
74+
left: 0.6rem;
75+
position: absolute;
76+
width: 0.4rem;
77+
top: 0.5rem;
78+
}
79+
}
80+
}

src/app/modules/shared/components/details-fields/details-fields.component.spec.ts

Lines changed: 53 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,46 @@
11
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2+
import { provideMockStore, MockStore } from '@ngrx/store/testing';
23
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
34

5+
import { TechnologyState } from '../../store/technology.reducers';
6+
import { allTechnologies } from '../../store/technology.selectors';
47
import { DetailsFieldsComponent } from './details-fields.component';
8+
import { FilterProjectPipe } from '../../../shared/pipes';
9+
import * as actions from '../../store/technology.actions';
510

611
describe('DetailsFieldsComponent', () => {
712
let component: DetailsFieldsComponent;
813
let fixture: ComponentFixture<DetailsFieldsComponent>;
14+
let store: MockStore<TechnologyState>;
15+
let mockTechnologySelector;
16+
17+
const state = {
18+
technologyList: { items: [{ name: 'java' }] },
19+
isLoading: false,
20+
};
21+
922
const initialData = {
1023
project: '',
1124
activity: '',
1225
ticket: '',
13-
technology: '',
14-
comments: ''
26+
comments: '',
1527
};
1628

1729
const newData = {
1830
project: 'Ernst&Young',
1931
activity: 'development',
2032
ticket: 'WA-15',
21-
technology: 'Angular',
22-
comments: 'No notes'
33+
comments: 'No notes',
2334
};
2435

2536
beforeEach(async(() => {
2637
TestBed.configureTestingModule({
27-
declarations: [DetailsFieldsComponent],
28-
imports: [
29-
FormsModule,
30-
ReactiveFormsModule
31-
],
38+
declarations: [DetailsFieldsComponent, FilterProjectPipe],
39+
providers: [provideMockStore({ initialState: state })],
40+
imports: [FormsModule, ReactiveFormsModule],
3241
}).compileComponents();
42+
store = TestBed.inject(MockStore);
43+
mockTechnologySelector = store.overrideSelector(allTechnologies, state);
3344
}));
3445

3546
beforeEach(() => {
@@ -59,4 +70,37 @@ describe('DetailsFieldsComponent', () => {
5970
component.ngOnChanges();
6071
expect(component.entryForm.value).toEqual(newData);
6172
});
73+
74+
it('should dispatch LoadTechnology action #getTechnologies', () => {
75+
const value = 'java';
76+
spyOn(store, 'dispatch');
77+
component.getTechnologies(value);
78+
79+
expect(store.dispatch).toHaveBeenCalledWith(new actions.LoadTechnology(value));
80+
});
81+
82+
it('should add a new tag #setTechnology', () => {
83+
const name = 'ngrx';
84+
component.selectedTechnology = ['java', 'javascript'];
85+
component.selectedTechnology.indexOf(name);
86+
component.selectedTechnology.length = 2;
87+
component.setTechnology(name);
88+
expect(component.selectedTechnology.length).toBe(3);
89+
});
90+
91+
it('should call the removeTag function #setTechnology', () => {
92+
const name = 'java';
93+
component.selectedTechnology = ['java', 'javascript'];
94+
const index = component.selectedTechnology.indexOf(name);
95+
spyOn(component, 'removeTag');
96+
component.setTechnology(name);
97+
expect(component.removeTag).toHaveBeenCalledWith(index);
98+
});
99+
100+
it('should call the removeTag() function #removeTag', () => {
101+
const index = 1;
102+
component.selectedTechnology = ['java', 'angular'];
103+
component.removeTag(index);
104+
expect(component.selectedTechnology.length).toBe(1);
105+
});
62106
});

src/app/modules/shared/components/details-fields/details-fields.component.ts

Lines changed: 46 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,78 @@
1-
import {
2-
Component,
3-
OnChanges,
4-
Input,
5-
Output,
6-
EventEmitter,
7-
ViewChild,
8-
ElementRef
9-
} from '@angular/core';
1+
import { Component, OnChanges, OnInit, Input, Output, EventEmitter, ViewChild, ElementRef } from '@angular/core';
102
import { FormBuilder, FormGroup } from '@angular/forms';
3+
import { Store, select } from '@ngrx/store';
4+
import * as actions from '../../store/technology.actions';
5+
6+
import { allTechnologies } from '../../store/technology.selectors';
7+
import { Technology } from '../../models';
118

129
@Component({
1310
selector: 'app-details-fields',
1411
templateUrl: './details-fields.component.html',
15-
styleUrls: ['./details-fields.component.scss']
12+
styleUrls: ['./details-fields.component.scss'],
1613
})
17-
export class DetailsFieldsComponent implements OnChanges {
14+
export class DetailsFieldsComponent implements OnChanges, OnInit {
1815
@Input() entryToEdit;
1916
@Input() formType: string;
2017
@Output() saveEntry = new EventEmitter();
2118
@ViewChild('closeModal') closeModal: ElementRef;
2219
entryForm: FormGroup;
20+
technologies: Technology;
21+
filterTechnology = '';
22+
selectedTechnology: string[] = [];
23+
isLoading = false;
2324

24-
constructor(private formBuilder: FormBuilder) {
25+
constructor(private formBuilder: FormBuilder, private store: Store<Technology>) {
2526
this.entryForm = this.formBuilder.group({
2627
project: '',
2728
activity: '',
2829
ticket: '',
29-
technology: '',
30-
comments: ''
30+
comments: '',
31+
});
32+
}
33+
34+
ngOnInit(): void {
35+
const technologies$ = this.store.pipe(select(allTechnologies));
36+
technologies$.subscribe((response) => {
37+
this.isLoading = response.isLoading;
38+
this.technologies = response.technologyList;
3139
});
3240
}
3341

3442
ngOnChanges(): void {
3543
if (this.entryToEdit) {
44+
this.selectedTechnology = this.entryToEdit.technology;
45+
3646
this.entryForm.setValue({
3747
project: this.entryToEdit.project,
3848
activity: this.entryToEdit.activity,
3949
ticket: this.entryToEdit.ticket,
40-
technology: this.entryToEdit.technology,
41-
comments: this.entryToEdit.comments
50+
comments: this.entryToEdit.comments,
4251
});
4352
}
4453
}
4554

55+
getTechnologies(value) {
56+
if (value.length >= 2) {
57+
this.store.dispatch(new actions.LoadTechnology(value));
58+
}
59+
}
60+
61+
setTechnology(name: string) {
62+
const index = this.selectedTechnology.indexOf(name);
63+
if (index > -1) {
64+
this.removeTag(index);
65+
} else {
66+
if (this.selectedTechnology.length < 10) {
67+
this.selectedTechnology = [...this.selectedTechnology, name];
68+
}
69+
}
70+
}
71+
72+
removeTag(index) {
73+
this.selectedTechnology.splice(index, 1);
74+
}
75+
4676
onSubmit() {
4777
this.saveEntry.emit(this.entryForm.value);
4878
this.closeModal.nativeElement.click();

src/app/modules/shared/components/modal/modal.component.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ describe('ModalComponent', () => {
3333
startDate: '2020-02-05T15:36:15.887Z',
3434
endDate: '2020-02-05T18:36:15.887Z',
3535
activity: 'development',
36-
technology: 'Angular, TypeScript',
36+
technology: ['Angular', 'TypeScript'],
3737
};
3838

3939
spyOn(component.removeList, 'emit');

src/app/modules/shared/models/entry.model.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ export interface Entry {
44
startDate: string;
55
endDate: string;
66
activity: string;
7-
technology: string;
7+
technology: string[];
88
comments?: string;
99
ticket?: string;
1010
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export * from './activity.model';
22
export * from './entry.model';
33
export * from './project.model';
4+
export * from './technology.model';
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export interface Technology {
2+
items: Array<any>;
3+
}

src/app/modules/shared/services/.gitkeep

Whitespace-only changes.

0 commit comments

Comments
 (0)