Skip to content

Commit 56ddc6b

Browse files
PaulRC-ioetAngeluz-07
authored andcommitted
feat: edit start time in time-clock #233
1 parent 85d5445 commit 56ddc6b

File tree

3 files changed

+142
-28
lines changed

3 files changed

+142
-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: 61 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,56 @@ 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+
}
119+
);
101120
expect(component.selectedTechnologies).toEqual([]);
102121
});
103122

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

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

Lines changed: 62 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,75 @@ 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_hour: formatDate(entryData.start_date, 'HH:mm:ss', 'en'),
79102
});
80103
if (entryData.technologies) {
81104
this.selectedTechnologies = entryData.technologies;
@@ -93,6 +116,19 @@ export class EntryFieldsComponent implements OnInit {
93116
this.store.dispatch(new entryActions.UpdateEntryRunning({ ...this.newData, ...this.entryForm.value }));
94117
}
95118

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

0 commit comments

Comments
 (0)