Skip to content

Commit 42a7a81

Browse files
authored
Merge pull request #559 from ioet/233_addTimeInOption
feat: edit start time in time-clock #233
2 parents 0cc88de + f56d2e6 commit 42a7a81

File tree

3 files changed

+144
-28
lines changed

3 files changed

+144
-28
lines changed

src/app/modules/time-clock/components/entry-fields/entry-fields.component.html

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,25 @@
2323
/>
2424
</div>
2525

26+
<div class="form-group">
27+
<label>Time in</label>
28+
<input
29+
[clearIfNotMatch]="true"
30+
[showMaskTyped]="true"
31+
[dropSpecialCharacters]="false"
32+
matInput
33+
mask="Hh:m0:s0"
34+
(blur)="onUpdateStartHour()"
35+
formControlName="start_hour"
36+
id="start_hour"
37+
type="text"
38+
class="form-control"
39+
aria-label="Small"
40+
[class.is-invalid]="start_hour.invalid && start_hour.touched"
41+
required
42+
/>
43+
</div>
44+
2645
<app-technologies
2746
(technologyAdded)="onTechnologyAdded($event)"
2847
(technologyRemoved)="onTechnologyRemoved($event)"

src/app/modules/time-clock/components/entry-fields/entry-fields.component.spec.ts

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ import {EntryFieldsComponent} from './entry-fields.component';
1010
import {ProjectState} from '../../../customer-management/components/projects/components/store/project.reducer';
1111
import {getCustomerProjects} from '../../../customer-management/components/projects/components/store/project.selectors';
1212
import { ActionsSubject } from '@ngrx/store';
13+
import { IndividualConfig, ToastrService } from 'ngx-toastr';
14+
import { formatDate } from '@angular/common';
15+
import * as moment from 'moment';
1316

1417
describe('EntryFieldsComponent', () => {
1518
type Merged = TechnologyState & ProjectState;
@@ -20,6 +23,10 @@ describe('EntryFieldsComponent', () => {
2023
let mockProjectsSelector;
2124
let entryForm;
2225
const actionSub: ActionsSubject = new ActionsSubject();
26+
const toastrServiceStub = {
27+
error: (message?: string, title?: string, override?: Partial<IndividualConfig>) => { },
28+
warning: (message?: string, title?: string, override?: Partial<IndividualConfig>) => { }
29+
};
2330

2431
const state = {
2532
projects: {
@@ -59,12 +66,18 @@ describe('EntryFieldsComponent', () => {
5966
project_id: 'project-id-15',
6067
description: 'description for active entry',
6168
uri: 'abc',
69+
start_date : moment().format('YYYY-MM-DD'),
70+
start_hour : moment().format('HH:mm:ss'),
6271
};
6372

6473
beforeEach(async(() => {
6574
TestBed.configureTestingModule({
6675
declarations: [EntryFieldsComponent],
67-
providers: [provideMockStore({initialState: state}), { provide: ActionsSubject, useValue: actionSub }],
76+
providers: [
77+
provideMockStore({initialState: state}),
78+
{ provide: ActionsSubject, useValue: actionSub },
79+
{ provide: ToastrService, useValue: toastrServiceStub }
80+
],
6881
imports: [FormsModule, ReactiveFormsModule],
6982
}).compileComponents();
7083
store = TestBed.inject(MockStore);
@@ -97,10 +110,57 @@ describe('EntryFieldsComponent', () => {
97110

98111
expect(component.entryForm.patchValue).toHaveBeenCalledTimes(1);
99112
expect(component.entryForm.patchValue).toHaveBeenCalledWith(
100-
{ description: entryDataForm.description, uri: entryDataForm.uri, activity_id: entryDataForm.activity_id });
113+
{
114+
description: entryDataForm.description,
115+
uri: entryDataForm.uri,
116+
activity_id: entryDataForm.activity_id,
117+
start_hour: formatDate(entry.start_date, 'HH:mm:ss', 'en'),
118+
start_date : moment().format('YYYY-MM-DD'),
119+
}
120+
);
101121
expect(component.selectedTechnologies).toEqual([]);
102122
});
103123

124+
it('displays error message when the date selected is in the future', () => {
125+
component.newData = entry;
126+
component.activeEntry = entry ;
127+
component.setDataToUpdate(entry);
128+
spyOn(toastrServiceStub, 'error');
129+
130+
const hourInTheFuture = moment().add(1, 'hours').format('HH:mm:ss');
131+
component.entryForm.patchValue({ start_hour : hourInTheFuture});
132+
component.onUpdateStartHour();
133+
134+
expect(toastrServiceStub.error).toHaveBeenCalled();
135+
});
136+
137+
it('If start hour is in the future, reset to initial start_date in form', () => {
138+
component.newData = entry;
139+
component.activeEntry = entry ;
140+
component.setDataToUpdate(entry);
141+
142+
const hourInTheFuture = moment().add(1, 'hours').format('HH:mm:ss');
143+
component.entryForm.patchValue({ start_hour : hourInTheFuture});
144+
145+
spyOn(component.entryForm, 'patchValue');
146+
component.onUpdateStartHour();
147+
148+
expect(component.entryForm.patchValue).toHaveBeenCalledWith(
149+
{
150+
start_hour: component.newData.start_hour
151+
}
152+
);
153+
});
154+
155+
it('when a start hour is updated, then dispatch UpdateActiveEntry', () => {
156+
component.activeEntry = entry ;
157+
component.setDataToUpdate(entry);
158+
spyOn(store, 'dispatch');
159+
160+
component.onUpdateStartHour();
161+
expect(store.dispatch).toHaveBeenCalled();
162+
});
163+
104164
it('when a technology is added, then dispatch UpdateActiveEntry', () => {
105165
const addedTechnologies = ['react'];
106166
spyOn(store, 'dispatch');

src/app/modules/time-clock/components/entry-fields/entry-fields.component.ts

Lines changed: 63 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ import { ActivityState, LoadActivities } from '../../../activities-management/st
1212

1313
import * as entryActions from '../../store/entry.actions';
1414

15+
import * as moment from 'moment';
16+
import { ToastrService } from 'ngx-toastr';
17+
import { formatDate } from '@angular/common';
18+
1519
type Merged = TechnologyState & ProjectState & ActivityState;
1620

1721
@Component({
@@ -26,56 +30,76 @@ export class EntryFieldsComponent implements OnInit {
2630
activeEntry;
2731
newData;
2832

29-
constructor(private formBuilder: FormBuilder, private store: Store<Merged>, private actionsSubject$: ActionsSubject) {
33+
constructor(
34+
private formBuilder: FormBuilder,
35+
private store: Store<Merged>,
36+
private actionsSubject$: ActionsSubject,
37+
private toastrService: ToastrService
38+
) {
3039
this.entryForm = this.formBuilder.group({
3140
description: '',
3241
uri: '',
3342
activity_id: '',
43+
start_hour: '',
44+
start_date: '',
3445
});
3546
}
3647

3748
ngOnInit(): void {
3849
this.store.dispatch(new LoadActivities());
3950

40-
this.actionsSubject$.pipe(
41-
filter((action: any) => (action.type === ActivityManagementActionTypes.LOAD_ACTIVITIES_SUCCESS))
42-
).subscribe((action) => {
43-
this.activities = action.payload;
44-
this.store.dispatch(new LoadActiveEntry());
45-
});
46-
47-
this.actionsSubject$.pipe(
48-
filter((action: any) => (action.type === EntryActionTypes.CREATE_ENTRY_SUCCESS))
49-
).subscribe((action) => {
50-
if (!action.payload.end_date) {
51+
this.actionsSubject$
52+
.pipe(filter((action: any) => action.type === ActivityManagementActionTypes.LOAD_ACTIVITIES_SUCCESS))
53+
.subscribe((action) => {
54+
this.activities = action.payload;
5155
this.store.dispatch(new LoadActiveEntry());
52-
}
53-
});
56+
});
5457

55-
this.actionsSubject$.pipe(
56-
filter((action: any) => ( action.type === EntryActionTypes.LOAD_ACTIVE_ENTRY_SUCCESS ))
57-
).subscribe((action) => {
58-
this.activeEntry = action.payload;
59-
this.setDataToUpdate(this.activeEntry);
60-
this.newData = {
61-
id: this.activeEntry.id,
62-
project_id: this.activeEntry.project_id,
63-
uri: this.activeEntry.uri,
64-
activity_id: this.activeEntry.activity_id,
65-
};
66-
});
58+
this.actionsSubject$
59+
.pipe(
60+
filter((action: any) => (
61+
action.type === EntryActionTypes.CREATE_ENTRY_SUCCESS ||
62+
action.type === EntryActionTypes.UPDATE_ENTRY_SUCCESS
63+
))
64+
).subscribe((action) => {
65+
if (!action.payload.end_date) {
66+
this.store.dispatch(new LoadActiveEntry());
67+
this.store.dispatch(new entryActions.LoadEntriesSummary());
68+
}
69+
});
70+
71+
this.actionsSubject$
72+
.pipe(filter((action: any) => action.type === EntryActionTypes.LOAD_ACTIVE_ENTRY_SUCCESS))
73+
.subscribe((action) => {
74+
this.activeEntry = action.payload;
75+
this.setDataToUpdate(this.activeEntry);
76+
this.newData = {
77+
id: this.activeEntry.id,
78+
project_id: this.activeEntry.project_id,
79+
uri: this.activeEntry.uri,
80+
activity_id: this.activeEntry.activity_id,
81+
start_date: this.activeEntry.start_date,
82+
start_hour: formatDate(this.activeEntry.start_date, 'HH:mm:ss', 'en'),
83+
};
84+
});
6785
}
6886

6987
get activity_id() {
7088
return this.entryForm.get('activity_id');
7189
}
7290

91+
get start_hour() {
92+
return this.entryForm.get('start_hour');
93+
}
94+
7395
setDataToUpdate(entryData: NewEntry) {
7496
if (entryData) {
7597
this.entryForm.patchValue({
7698
description: entryData.description,
7799
uri: entryData.uri,
78100
activity_id: entryData.activity_id,
101+
start_date: entryData.start_date,
102+
start_hour: formatDate(entryData.start_date, 'HH:mm:ss', 'en'),
79103
});
80104
if (entryData.technologies) {
81105
this.selectedTechnologies = entryData.technologies;
@@ -93,6 +117,19 @@ export class EntryFieldsComponent implements OnInit {
93117
this.store.dispatch(new entryActions.UpdateEntryRunning({ ...this.newData, ...this.entryForm.value }));
94118
}
95119

120+
onUpdateStartHour() {
121+
const startDate = formatDate(this.activeEntry.start_date, 'yyyy-MM-dd', 'en');
122+
const newHourEntered = new Date(`${startDate}T${this.entryForm.value.start_hour.trim()}`).toISOString();
123+
const isEntryDateInTheFuture = moment(newHourEntered).isAfter(moment());
124+
if (isEntryDateInTheFuture) {
125+
this.toastrService.error('You cannot start a time-entry in the future');
126+
this.entryForm.patchValue({ start_hour: this.newData.start_hour });
127+
return;
128+
}
129+
this.entryForm.patchValue({ start_date: newHourEntered });
130+
this.store.dispatch(new entryActions.UpdateEntryRunning({ ...this.newData, ...this.entryForm.value }));
131+
}
132+
96133
onTechnologyAdded($event: string[]) {
97134
this.store.dispatch(new entryActions.UpdateEntryRunning({ ...this.newData, technologies: $event }));
98135
}

0 commit comments

Comments
 (0)