Skip to content

Commit 974042d

Browse files
authored
Merge pull request #375 from ioet/333-allow-edit-entry-running
333 allow edit entry running
2 parents c828171 + a3907c6 commit 974042d

File tree

8 files changed

+165
-61
lines changed

8 files changed

+165
-61
lines changed

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

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
<form [formGroup]="entryForm" (ngSubmit)="onSubmit()">
2+
<label><input id='isEntryRunning' type="checkbox" (change)="onIsRunningChange($event)" [checked]="isEntryRunning"> I am working on
3+
this</label>
4+
25
<div class="input-group input-group-sm mb-3">
6+
37
<div class="input-group-prepend">
48
<span class="input-group-text span-width">Project</span>
59
</div>
@@ -11,13 +15,14 @@
1115
formControlName="project_id"
1216
>
1317
<option value="" selected="selected"></option>
14-
<option *ngFor="let project of listProjects" value="{{ project.id }}">{{ project.customer_name }} - {{ project.name }}</option>
18+
<option *ngFor="let project of listProjects" value="{{ project.id }}">{{ project.customer_name }}
19+
- {{ project.name }}</option>
1520
</select>
1621
</div>
1722

1823
<div class="input-group input-group-sm mb-3">
1924
<div class="input-group-prepend">
20-
<span class="input-group-text span-width" id="inputGroup-sizing-sm">Activity</span>
25+
<span class="input-group-text span-width">Activity</span>
2126
</div>
2227
<select
2328
[class.is-invalid]="activity_id.invalid && activity_id.touched"
@@ -37,7 +42,7 @@
3742

3843
<div class="input-group input-group-sm mb-3">
3944
<div class="input-group-prepend">
40-
<span class="input-group-text span-width" id="inputGroup-sizing-sm">Ticket</span>
45+
<span class="input-group-text span-width">Ticket</span>
4146
</div>
4247
<input
4348
formControlName="uri"
@@ -51,7 +56,7 @@
5156

5257
<div class="input-group input-group-sm mb-3">
5358
<div class="input-group-prepend">
54-
<span class="input-group-text span-width" id="inputGroup-sizing-sm">Date</span>
59+
<span class="input-group-text span-width">Date</span>
5560
</div>
5661
<input
5762
formControlName="entry_date"
@@ -66,11 +71,11 @@
6671
</div>
6772
<div class="input-group input-group-sm mb-3">
6873
<div class="input-group-prepend">
69-
<span class="input-group-text span-width" id="inputGroup-sizing-sm">Time in</span>
74+
<span class="input-group-text span-width">Time in</span>
7075
</div>
7176
<input
72-
[clearIfNotMatch]="true"
73-
[showMaskTyped] = "true"
77+
[clearIfNotMatch]="true"
78+
[showMaskTyped]="true"
7479
[dropSpecialCharacters]="false"
7580
matInput
7681
mask="Hh:m0"
@@ -83,12 +88,13 @@
8388
required
8489
aria-describedby="inputGroup-sizing-sm"
8590
/>
86-
<div class="input-group-prepend">
87-
<span class="input-group-text span-width" id="inputGroup-sizing-sm">Time out</span>
91+
<div class="input-group-prepend" *ngIf="!isEntryRunning">
92+
<span class="input-group-text span-width">Time out</span>
8893
</div>
8994
<input
90-
[clearIfNotMatch]="true"
91-
[showMaskTyped] = "true"
95+
*ngIf="!isEntryRunning"
96+
[clearIfNotMatch]="true"
97+
[showMaskTyped]="true"
9298
[dropSpecialCharacters]="false"
9399
matInput
94100
mask="Hh:m0"
@@ -112,11 +118,12 @@
112118

113119
<div class="form-group text-left">
114120
<label for="NotesTextarea">Description</label>
115-
<textarea maxlength="1500" formControlName="description" class="form-control" id="NotesTextarea" rows="3"></textarea>
121+
<textarea maxlength="1500" formControlName="description" class="form-control" id="NotesTextarea"
122+
rows="3"></textarea>
116123
</div>
117124
<div class="modal-footer">
118125
<button type="submit" class="btn btn-primary" [disabled]="!entryForm.valid">Save</button>
119-
<button #closeModal type="button" class="btn btn-secondary" data-dismiss="modal" >
126+
<button #closeModal type="button" class="btn btn-secondary" data-dismiss="modal">
120127
Close
121128
</button>
122129
</div>

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

Lines changed: 103 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { TechnologiesComponent } from './../technologies/technologies.component'
22
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
33
import { provideMockStore, MockStore } from '@ngrx/store/testing';
44
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
5-
import { formatDate } from '@angular/common';
5+
import { DatePipe, formatDate } from '@angular/common';
66

77
import { TechnologyState } from '../../store/technology.reducers';
88
import { allTechnologies } from '../../store/technology.selectors';
@@ -22,21 +22,23 @@ describe('DetailsFieldsComponent', () => {
2222
let mockProjectsSelector;
2323
let mockEntriesUpdateErrorSelector;
2424
let mockEntriesCreateErrorSelector;
25+
let entryToEdit;
26+
let formValues;
2527

2628
const state = {
2729
projects: {
28-
projects: [{ id: 'id', name: 'name', project_type_id: '' }],
29-
customerProjects: [{ id: 'id', name: 'name', description: 'description', project_type_id: '123' }],
30+
projects: [{id: 'id', name: 'name', project_type_id: ''}],
31+
customerProjects: [{id: 'id', name: 'name', description: 'description', project_type_id: '123'}],
3032
isLoading: false,
3133
message: '',
3234
projectToEdit: undefined,
3335
},
3436
technologies: {
35-
technologyList: { items: [{ name: 'java' }] },
37+
technologyList: {items: [{name: 'java'}]},
3638
isLoading: false,
3739
},
3840
activities: {
39-
data: [{ id: 'fc5fab41-a21e-4155-9d05-511b956ebd05', tenant_id: 'ioet', deleted: null, name: 'abc' }],
41+
data: [{id: 'fc5fab41-a21e-4155-9d05-511b956ebd05', tenant_id: 'ioet', deleted: null, name: 'abc'}],
4042
isLoading: false,
4143
message: 'Data fetch successfully!',
4244
activityIdToEdit: '',
@@ -61,7 +63,7 @@ describe('DetailsFieldsComponent', () => {
6163
beforeEach(async(() => {
6264
TestBed.configureTestingModule({
6365
declarations: [DetailsFieldsComponent, TechnologiesComponent],
64-
providers: [provideMockStore({ initialState: state })],
66+
providers: [provideMockStore({initialState: state})],
6567
imports: [FormsModule, ReactiveFormsModule],
6668
}).compileComponents();
6769
store = TestBed.inject(MockStore);
@@ -74,28 +76,17 @@ describe('DetailsFieldsComponent', () => {
7476
beforeEach(() => {
7577
fixture = TestBed.createComponent(DetailsFieldsComponent);
7678
component = fixture.componentInstance;
77-
});
78-
79-
it('should create', () => {
80-
expect(component).toBeTruthy();
81-
});
82-
83-
it('should emit ngOnChange without data', () => {
84-
component.entryToEdit = null;
85-
component.ngOnChanges();
86-
expect(component.entryForm.value).toEqual(initialData);
87-
});
88-
89-
it('should emit ngOnChange with new data', () => {
90-
const entryToEdit = {
79+
entryToEdit = {
9180
project_id: '',
9281
activity_id: '',
9382
uri: 'ticketUri',
9483
start_date: null,
9584
end_date: null,
9685
description: '',
86+
technologies: [],
87+
id: 'xyz'
9788
};
98-
const formValue = {
89+
formValues = {
9990
project_id: '',
10091
activity_id: '',
10192
uri: 'ticketUri',
@@ -105,9 +96,24 @@ describe('DetailsFieldsComponent', () => {
10596
description: '',
10697
technology: '',
10798
};
99+
});
100+
101+
it('should create', () => {
102+
expect(component).toBeTruthy();
103+
});
104+
105+
it('should emit ngOnChange without data', () => {
106+
component.entryToEdit = null;
107+
component.ngOnChanges();
108+
expect(component.entryForm.value).toEqual(initialData);
109+
});
110+
111+
it('should emit ngOnChange with new data', () => {
108112
component.entryToEdit = entryToEdit;
113+
109114
component.ngOnChanges();
110-
expect(component.entryForm.value).toEqual(formValue);
115+
116+
expect(component.entryForm.value).toEqual(formValues);
111117
});
112118

113119
it('should emit ngOnChange with new data', () => {
@@ -170,4 +176,79 @@ describe('DetailsFieldsComponent', () => {
170176
};
171177
expect(component.saveEntry.emit).toHaveBeenCalledWith(data);
172178
});
179+
180+
it('when the current entry is not running, then the end hour input should be rendered', () => {
181+
component.isEntryRunning = false;
182+
fixture.detectChanges();
183+
184+
const endHourInput = fixture.debugElement.nativeElement.querySelector('#end_hour');
185+
expect(endHourInput).toBeDefined();
186+
});
187+
188+
it('when the current entry is running, then the end hour input should not be rendered', () => {
189+
component.isEntryRunning = true;
190+
fixture.detectChanges();
191+
192+
const endHourInput = fixture.debugElement.nativeElement.querySelector('#end_hour');
193+
expect(endHourInput).toBeNull();
194+
});
195+
196+
it('when creating a new entry, then the new entry should be marked as not running', () => {
197+
component.entryToEdit = null;
198+
199+
expect(component.isEntryRunning).toBeFalse();
200+
});
201+
202+
it('when editing entry that is currently running, then the entry should be marked as running', () => {
203+
component.entryToEdit = {...entryToEdit, running: true};
204+
205+
fixture.componentInstance.ngOnChanges();
206+
207+
expect(component.isEntryRunning).toBeTrue();
208+
});
209+
210+
it('when editing entry that already finished, then the entry should not be marked as running', () => {
211+
component.entryToEdit = {...entryToEdit, running: false};
212+
213+
fixture.componentInstance.ngOnChanges();
214+
215+
expect(component.isEntryRunning).toBeFalse();
216+
});
217+
218+
it('when editing entry that already finished, then the entry should not be marked as running', () => {
219+
component.entryToEdit = {...entryToEdit, running: false};
220+
221+
fixture.componentInstance.ngOnChanges();
222+
223+
expect(component.isEntryRunning).toBeFalse();
224+
});
225+
226+
it('when submitting a entry that is currently running, the end date should not be sent ', () => {
227+
component.isEntryRunning = true;
228+
spyOn(component.saveEntry, 'emit');
229+
230+
component.entryForm.setValue({...formValues, entry_date: '2020-06-11'});
231+
component.onSubmit();
232+
const data = {
233+
project_id: '',
234+
activity_id: '',
235+
technologies: [],
236+
description: '',
237+
start_date: '2020-06-11T00:00',
238+
uri: 'ticketUri',
239+
};
240+
expect(component.saveEntry.emit).toHaveBeenCalledWith(data);
241+
});
242+
243+
it('when disabling current entry is running, then the end hour should be set to the current time', () => {
244+
const datePipe: DatePipe = new DatePipe('en');
245+
const currentTime = datePipe.transform(new Date(), 'HH:mm');
246+
247+
const checkIsEntryRunning: Element = fixture.debugElement.nativeElement.querySelector('#isEntryRunning');
248+
checkIsEntryRunning.dispatchEvent(new Event('change'));
249+
fixture.detectChanges();
250+
251+
const endHourInput: HTMLInputElement = fixture.debugElement.nativeElement.querySelector('#end_hour');
252+
expect(endHourInput.value).toEqual(currentTime);
253+
});
173254
});

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

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,18 @@
1-
import {
2-
Component,
3-
OnChanges,
4-
OnInit,
5-
Input,
6-
Output,
7-
EventEmitter,
8-
ViewChild,
9-
ElementRef,
10-
} from '@angular/core';
1+
import { Component, ElementRef, EventEmitter, Input, OnChanges, OnInit, Output, ViewChild, } from '@angular/core';
112
import { FormBuilder, FormGroup } from '@angular/forms';
12-
import { Store, select } from '@ngrx/store';
3+
import { select, Store } from '@ngrx/store';
134
import { formatDate } from '@angular/common';
145

15-
import { Project, Activity } from '../../models';
6+
import { Activity, Entry, Project } from '../../models';
167
import { ProjectState } from '../../../customer-management/components/projects/components/store/project.reducer';
178
import { TechnologyState } from '../../store/technology.reducers';
18-
import { LoadActivities, ActivityState, allActivities } from '../../../activities-management/store';
9+
import { ActivityState, allActivities, LoadActivities } from '../../../activities-management/store';
1910
import { getProjects } from '../../../customer-management/components/projects/components/store/project.selectors';
2011
import * as projectActions from '../../../customer-management/components/projects/components/store/project.actions';
2112
import { EntryState } from '../../../time-clock/store/entry.reducer';
2213
import * as entryActions from '../../../time-clock/store/entry.actions';
23-
import { getUpdateError, getCreateError } from 'src/app/modules/time-clock/store/entry.selectors';
14+
import { getCreateError, getUpdateError } from 'src/app/modules/time-clock/store/entry.selectors';
15+
2416
type Merged = TechnologyState & ProjectState & ActivityState & EntryState;
2517

2618
@Component({
@@ -29,7 +21,7 @@ type Merged = TechnologyState & ProjectState & ActivityState & EntryState;
2921
styleUrls: ['./details-fields.component.scss'],
3022
})
3123
export class DetailsFieldsComponent implements OnChanges, OnInit {
32-
@Input() entryToEdit;
24+
@Input() entryToEdit: Entry;
3325
@Input() formType: string;
3426
@Output() saveEntry = new EventEmitter();
3527
@ViewChild('closeModal') closeModal: ElementRef;
@@ -38,8 +30,7 @@ export class DetailsFieldsComponent implements OnChanges, OnInit {
3830
isLoading = false;
3931
listProjects: Project[] = [];
4032
activities: Activity[] = [];
41-
keyword = 'name';
42-
showlist: boolean;
33+
isEntryRunning = false;
4334

4435
constructor(private formBuilder: FormBuilder, private store: Store<Merged>) {
4536
this.entryForm = this.formBuilder.group({
@@ -84,6 +75,7 @@ export class DetailsFieldsComponent implements OnChanges, OnInit {
8475
}
8576

8677
ngOnChanges(): void {
78+
this.isEntryRunning = this.entryToEdit ? this.entryToEdit.running : false;
8779
if (this.entryToEdit) {
8880
this.selectedTechnologies = this.entryToEdit.technologies;
8981
this.entryForm.setValue({
@@ -156,6 +148,16 @@ export class DetailsFieldsComponent implements OnChanges, OnInit {
156148
end_date: `${entryDate}T${this.entryForm.value.end_hour.trim()}`,
157149
uri: this.entryForm.value.uri,
158150
};
151+
if (this.isEntryRunning) {
152+
delete entry.end_date;
153+
}
159154
this.saveEntry.emit(entry);
160155
}
156+
157+
onIsRunningChange(event: any) {
158+
this.isEntryRunning = event.currentTarget.checked;
159+
if (!this.isEntryRunning) {
160+
this.entryForm.patchValue({end_hour: formatDate(new Date(), 'HH:mm', 'en')});
161+
}
162+
}
161163
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export interface Entry {
2+
running?: boolean;
23
id: string;
34
start_date: Date;
45
end_date: Date;
@@ -8,6 +9,7 @@ export interface Entry {
89
uri?: string;
910
project_id?: string;
1011
owner_email?: string;
12+
description?: string;
1113
}
1214

1315
export interface NewEntry {

src/app/modules/time-clock/pipes/time-details.pipe.spec.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { TimeDetails } from './../models/time.entry.summary';
21
import { TimeDetailsPipe } from './time-details.pipe';
32

43
describe('TimeDetailsPipe', () => {

0 commit comments

Comments
 (0)