Skip to content

Commit 87171ae

Browse files
author
Rene Enriquez
committed
fix: #450 autocomplete on time-entries page
1 parent 9f83f4f commit 87171ae

File tree

5 files changed

+110
-19
lines changed

5 files changed

+110
-19
lines changed

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

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,37 @@
77
<div class="input-group-prepend">
88
<span class="input-group-text span-width">Project</span>
99
</div>
10-
<select
11-
[class.is-invalid]="project_id.invalid"
12-
required
13-
id="project_id"
14-
class="custom-select"
15-
formControlName="project_id"
16-
>
17-
<option value="" selected="selected"></option>
18-
<option *ngFor="let project of listProjects" value="{{ project.id }}">{{ project.customer_name }}
19-
- {{ project.name }}</option>
20-
</select>
10+
11+
<div class="form-control autocomplete">
12+
<ng-autocomplete
13+
(selected)='onSelectedProject($event)'
14+
(inputCleared)='onClearedComponent($event)'
15+
formControlName="project_name"
16+
[data]="listProjects"
17+
[searchKeyword]="keyword"
18+
historyIdentifier="projectsSelected"
19+
notFoundText="No projects found"
20+
placeHolder="Enter the project name"
21+
[itemTemplate]="itemTemplate"
22+
[notFoundTemplate]="notFoundTemplate">
23+
</ng-autocomplete>
24+
25+
<ng-template #itemTemplate let-item>
26+
<div class="d-flex container">
27+
<div class="mr-auto p-2">
28+
<span [innerHTML]="item.customer_name"></span> -
29+
<strong><span [innerHTML]="item.name"></span></strong>
30+
</div>
31+
</div>
32+
</ng-template>
33+
34+
<ng-template #notFoundTemplate let-notFound>
35+
<div [innerHTML]="notFound"></div>
36+
</ng-template>
37+
<!-- <app-loading-bar *ngIf="(isLoading$ | async)"></app-loading-bar> -->
38+
</div>
39+
40+
2141
</div>
2242

2343
<div class="input-group input-group-sm mb-3">

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,3 +107,17 @@ input[type="date"]::-webkit-inner-spin-button {
107107
input[type="date"]::-webkit-clear-button {
108108
display: none;
109109
}
110+
111+
.container {
112+
font-size: small;
113+
cursor: text;
114+
}
115+
116+
.autocomplete {
117+
width: 80%;
118+
display: block;
119+
background-clip: padding-box;
120+
border: none;
121+
margin: 0em;
122+
padding: 0em;
123+
}

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

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { AutocompleteLibModule } from 'angular-ng-autocomplete';
12
import { formatDate } from '@angular/common';
23
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
34
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
@@ -29,7 +30,8 @@ describe('DetailsFieldsComponent', () => {
2930
let formValues;
3031
const actionSub: ActionsSubject = new ActionsSubject();
3132
const toastrServiceStub = {
32-
error: (message?: string, title?: string, override?: Partial<IndividualConfig>) => { }
33+
error: (message?: string, title?: string, override?: Partial<IndividualConfig>) => { },
34+
warning: (message?: string, title?: string, override?: Partial<IndividualConfig>) => { }
3335
};
3436

3537
const state = {
@@ -58,6 +60,7 @@ describe('DetailsFieldsComponent', () => {
5860

5961
const initialData = {
6062
project_id: '',
63+
project_name: '',
6164
activity_id: '',
6265
uri: '',
6366
entry_date: formatDate(new Date(), 'yyyy-MM-dd', 'en'),
@@ -75,7 +78,7 @@ describe('DetailsFieldsComponent', () => {
7578
{ provide: ActionsSubject, useValue: actionSub },
7679
{ provide: ToastrService, useValue: toastrServiceStub }
7780
],
78-
imports: [FormsModule, ReactiveFormsModule],
81+
imports: [FormsModule, ReactiveFormsModule, AutocompleteLibModule],
7982
}).compileComponents();
8083
store = TestBed.inject(MockStore);
8184
mockTechnologySelector = store.overrideSelector(allTechnologies, state.technologies);
@@ -88,7 +91,7 @@ describe('DetailsFieldsComponent', () => {
8891
fixture = TestBed.createComponent(DetailsFieldsComponent);
8992
component = fixture.componentInstance;
9093
entryToEdit = {
91-
project_id: '',
94+
project_id: 'id',
9295
activity_id: '',
9396
uri: 'ticketUri',
9497
start_date: null,
@@ -98,7 +101,8 @@ describe('DetailsFieldsComponent', () => {
98101
id: 'xyz'
99102
};
100103
formValues = {
101-
project_id: 'p1',
104+
project_id: 'id',
105+
project_name: 'name',
102106
activity_id: 'a1',
103107
uri: 'ticketUri',
104108
entry_date: '',
@@ -114,6 +118,21 @@ describe('DetailsFieldsComponent', () => {
114118
expect(component).toBeTruthy();
115119
});
116120

121+
it('onClearedComponent project id and name it is set to empty', () => {
122+
component.onClearedComponent(null);
123+
124+
expect(component.project_id.value).toBe('');
125+
expect(component.project_name.value).toBe('');
126+
});
127+
128+
it('onSelectedProject project id and name it is set using event data', () => {
129+
spyOn(component.entryForm, 'patchValue');
130+
131+
component.onSelectedProject( {id: 'id', search_field: 'foo'} );
132+
133+
expect(component.entryForm.patchValue).toHaveBeenCalledWith( { project_id: 'id', project_name: 'foo', } );
134+
});
135+
117136
it('if form is invalid then no save is emited', () => {
118137
spyOn(component.saveEntry, 'emit');
119138

@@ -151,6 +170,7 @@ describe('DetailsFieldsComponent', () => {
151170
component.closeModal = childComponent;
152171
const formValue = {
153172
project_id: '',
173+
project_name: '',
154174
activity_id: '',
155175
uri: '',
156176
entry_date: formatDate(new Date(), 'yyyy-MM-dd', 'en'),
@@ -186,6 +206,7 @@ describe('DetailsFieldsComponent', () => {
186206
spyOn(component.saveEntry, 'emit');
187207
component.entryForm.setValue({
188208
project_id: 'p1',
209+
project_name: 'p-name',
189210
activity_id: 'a1',
190211
uri: '',
191212
entry_date: '2020-02-05',
@@ -269,7 +290,7 @@ describe('DetailsFieldsComponent', () => {
269290

270291
const data: SaveEntryEvent = {
271292
entry: {
272-
project_id: 'p1',
293+
project_id: 'id',
273294
activity_id: 'a1',
274295
technologies: [],
275296
description: '',

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

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ type Merged = TechnologyState & ProjectState & ActivityState & EntryState;
2323
styleUrls: ['./details-fields.component.scss'],
2424
})
2525
export class DetailsFieldsComponent implements OnChanges, OnInit {
26+
27+
keyword = 'search_field';
2628
@Input() entryToEdit: Entry;
2729
@Input() canMarkEntryAsWIP: boolean;
2830
@Output() saveEntry = new EventEmitter<SaveEntryEvent>();
@@ -39,6 +41,7 @@ export class DetailsFieldsComponent implements OnChanges, OnInit {
3941
private actionsSubject$: ActionsSubject, private toastrService: ToastrService) {
4042
this.entryForm = this.formBuilder.group({
4143
project_id: ['', Validators.required],
44+
project_name: ['', Validators.required],
4245
activity_id: ['', Validators.required],
4346
description: '',
4447
entry_date: '',
@@ -52,8 +55,16 @@ export class DetailsFieldsComponent implements OnChanges, OnInit {
5255
ngOnInit(): void {
5356
this.store.dispatch(new projectActions.LoadProjects());
5457
const projects$ = this.store.pipe(select(getProjects));
55-
projects$.subscribe((response) => {
56-
this.listProjects = response;
58+
projects$.subscribe((projects) => {
59+
if (projects) {
60+
this.listProjects = [];
61+
projects.forEach((project) => {
62+
const projectWithSearchField = {...project};
63+
projectWithSearchField.search_field = `${project.customer_name} - ${project.name}`;
64+
this.listProjects.push(projectWithSearchField);
65+
}
66+
);
67+
}
5768
});
5869

5970
this.store.dispatch(new LoadActivities());
@@ -87,11 +98,29 @@ export class DetailsFieldsComponent implements OnChanges, OnInit {
8798
});
8899
}
89100

101+
onClearedComponent(event) {
102+
this.entryForm.patchValue(
103+
{
104+
project_id: '',
105+
project_name: '',
106+
});
107+
}
108+
109+
onSelectedProject(item) {
110+
this.entryForm.patchValue(
111+
{
112+
project_id: item.id,
113+
project_name: item.search_field,
114+
});
115+
}
116+
90117
ngOnChanges(): void {
91118
this.goingToWorkOnThis = this.entryToEdit ? this.entryToEdit.running : false;
92119
if (this.entryToEdit) {
93120
this.selectedTechnologies = this.entryToEdit.technologies;
121+
const projectFound = this.listProjects.find((project) => project.id === this.entryToEdit.project_id);
94122
this.entryForm.setValue({
123+
project_name: projectFound ? projectFound.search_field : '',
95124
project_id: this.entryToEdit.project_id,
96125
activity_id: this.entryToEdit.activity_id,
97126
description: this.entryToEdit.description,
@@ -109,6 +138,7 @@ export class DetailsFieldsComponent implements OnChanges, OnInit {
109138
cleanForm() {
110139
this.selectedTechnologies = [];
111140
this.entryForm.setValue({
141+
project_name: '',
112142
project_id: '',
113143
activity_id: '',
114144
description: '',
@@ -128,6 +158,10 @@ export class DetailsFieldsComponent implements OnChanges, OnInit {
128158
return this.entryForm.get('project_id');
129159
}
130160

161+
get project_name() {
162+
return this.entryForm.get('project_name');
163+
}
164+
131165
get activity_id() {
132166
return this.entryForm.get('activity_id');
133167
}
@@ -151,6 +185,7 @@ export class DetailsFieldsComponent implements OnChanges, OnInit {
151185

152186
onSubmit() {
153187
if (this.entryForm.invalid) {
188+
this.toastrService.warning('Make sure to select a project and activity');
154189
return;
155190
}
156191
// start&end date same for now

src/app/modules/time-entries/pages/time-entries.component.spec.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { AutocompleteLibModule } from 'angular-ng-autocomplete';
12
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
23
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
34
import { MockStore, provideMockStore } from '@ngrx/store/testing';
@@ -46,7 +47,7 @@ describe('TimeEntriesComponent', () => {
4647
providers: [provideMockStore({ initialState: state }),
4748
{ provide: ToastrService, useValue: toastrService },
4849
],
49-
imports: [FormsModule, ReactiveFormsModule],
50+
imports: [FormsModule, ReactiveFormsModule, AutocompleteLibModule],
5051
}).compileComponents();
5152
store = TestBed.inject(MockStore);
5253
entry = {

0 commit comments

Comments
 (0)