Skip to content

Commit 79e991a

Browse files
committed
fix: #572 allow manual editing in time in when switch project
1 parent 7e696e1 commit 79e991a

File tree

3 files changed

+121
-7
lines changed

3 files changed

+121
-7
lines changed

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

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ describe('EntryFieldsComponent', () => {
2727
error: (message?: string, title?: string, override?: Partial<IndividualConfig>) => { },
2828
warning: (message?: string, title?: string, override?: Partial<IndividualConfig>) => { }
2929
};
30+
const lastDate = moment().format('YYYY-MM-DD');
31+
const startHourTest = moment().add(-5, 'hours').format('HH:mm:ss');
32+
const endHourTest = moment().add(-3, 'hours').format('HH:mm:ss');
33+
const lastStartHourEntryEntered = new Date(`${lastDate}T${startHourTest.trim()}`).toISOString();
34+
const lastEndHourEntryEntered = new Date(`${lastDate}T${endHourTest.trim()}`).toISOString();
3035

3136
const state = {
3237
projects: {
@@ -57,6 +62,28 @@ describe('EntryFieldsComponent', () => {
5762
},
5863
entryList: [],
5964
message: '',
65+
timeEntriesDataSource: { data: [
66+
{
67+
activity_id: 'xyz',
68+
activity_name: 'abc',
69+
id: 'id-15',
70+
project_id: 'project-id-15',
71+
description: 'description for an entry',
72+
uri: 'abc',
73+
start_date : moment().toISOString(),
74+
end_date : moment().toISOString(),
75+
},
76+
{
77+
activity_id: 'xyz',
78+
activity_name: 'abc',
79+
id: 'id-15',
80+
project_id: 'project-id-15',
81+
description: 'description for an entry',
82+
uri: 'abc',
83+
start_date : lastStartHourEntryEntered,
84+
end_date : lastEndHourEntryEntered,
85+
}
86+
]}
6087
},
6188
};
6289

@@ -134,6 +161,37 @@ describe('EntryFieldsComponent', () => {
134161
expect(toastrServiceStub.error).toHaveBeenCalled();
135162
});
136163

164+
it('displays error message when new hour entered is in the past of other entry', () => {
165+
component.newData = entry;
166+
component.activeEntry = entry ;
167+
component.setDataToUpdate(entry);
168+
spyOn(toastrServiceStub, 'error');
169+
170+
const hourInTheFuture = moment().add(-6, 'hour').format('HH:mm:ss');
171+
component.entryForm.patchValue({ start_hour : hourInTheFuture});
172+
component.onUpdateStartHour();
173+
174+
expect(toastrServiceStub.error).toHaveBeenCalled();
175+
});
176+
177+
it('If start hour is in the past of other entry, reset to initial start_date in form', () => {
178+
component.newData = entry;
179+
component.activeEntry = entry ;
180+
component.setDataToUpdate(entry);
181+
182+
const newHour = moment().add(-6, 'hours').format('HH:mm:ss');
183+
component.entryForm.patchValue({ start_hour : newHour});
184+
185+
spyOn(component.entryForm, 'patchValue');
186+
component.onUpdateStartHour();
187+
188+
expect(component.entryForm.patchValue).toHaveBeenCalledWith(
189+
{
190+
start_hour: component.newData.start_hour
191+
}
192+
);
193+
});
194+
137195
it('If start hour is in the future, reset to initial start_date in form', () => {
138196
component.newData = entry;
139197
component.activeEntry = entry ;
@@ -155,12 +213,37 @@ describe('EntryFieldsComponent', () => {
155213
it('when a start hour is updated, then dispatch UpdateActiveEntry', () => {
156214
component.activeEntry = entry ;
157215
component.setDataToUpdate(entry);
216+
const newHour = moment().format('HH:mm:ss');
217+
component.entryForm.patchValue({ start_hour : newHour});
158218
spyOn(store, 'dispatch');
159219

160220
component.onUpdateStartHour();
161221
expect(store.dispatch).toHaveBeenCalled();
162222
});
163223

224+
it('when a start hour is update, then select the last time entry', async(() => {
225+
component.activeEntry = entry ;
226+
component.setDataToUpdate(entry);
227+
const newHour = moment().format('HH:mm:ss');
228+
229+
component.entryForm.patchValue({ start_hour : newHour});
230+
component.onUpdateStartHour();
231+
232+
expect(component.lastEntry).toBe(state.entries.timeEntriesDataSource.data[1]);
233+
}));
234+
235+
it('when a start hour is updated in other time entry, then dispatch UpdateEntry and UpdateEntryRunning', () => {
236+
component.activeEntry = entry ;
237+
component.setDataToUpdate(entry);
238+
239+
const newHour = moment().add(-4, 'hours').format('HH:mm:ss');
240+
component.entryForm.patchValue({ start_hour : newHour});
241+
spyOn(store, 'dispatch');
242+
243+
component.onUpdateStartHour();
244+
expect(store.dispatch).toHaveBeenCalledTimes(2);
245+
});
246+
164247
it('when a technology is added, then dispatch UpdateActiveEntry', () => {
165248
const addedTechnologies = ['react'];
166249
spyOn(store, 'dispatch');

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

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@ import { EntryActionTypes, LoadActiveEntry } from './../../store/entry.actions';
33
import { filter } from 'rxjs/operators';
44
import { Component, OnInit } from '@angular/core';
55
import { FormBuilder, FormGroup } from '@angular/forms';
6-
import { Store, ActionsSubject } from '@ngrx/store';
7-
6+
import { Store, ActionsSubject, select } from '@ngrx/store';
87
import { Activity, NewEntry } from '../../../shared/models';
98
import { ProjectState } from '../../../customer-management/components/projects/components/store/project.reducer';
109
import { TechnologyState } from '../../../shared/store/technology.reducers';
@@ -15,6 +14,7 @@ import * as entryActions from '../../store/entry.actions';
1514
import * as moment from 'moment';
1615
import { ToastrService } from 'ngx-toastr';
1716
import { formatDate } from '@angular/common';
17+
import { getTimeEntriesDataSource } from '../../store/entry.selectors';
1818

1919
type Merged = TechnologyState & ProjectState & ActivityState;
2020

@@ -29,13 +29,15 @@ export class EntryFieldsComponent implements OnInit {
2929
activities: Activity[] = [];
3030
activeEntry;
3131
newData;
32+
lastEntry;
3233

3334
constructor(
3435
private formBuilder: FormBuilder,
3536
private store: Store<Merged>,
3637
private actionsSubject$: ActionsSubject,
3738
private toastrService: ToastrService
3839
) {
40+
this.store.dispatch(new entryActions.LoadEntries(new Date().getMonth() + 1));
3941
this.entryForm = this.formBuilder.group({
4042
description: '',
4143
uri: '',
@@ -57,11 +59,13 @@ export class EntryFieldsComponent implements OnInit {
5759

5860
this.actionsSubject$
5961
.pipe(
60-
filter((action: any) => (
61-
action.type === EntryActionTypes.CREATE_ENTRY_SUCCESS ||
62-
action.type === EntryActionTypes.UPDATE_ENTRY_SUCCESS
63-
))
64-
).subscribe((action) => {
62+
filter(
63+
(action: any) =>
64+
action.type === EntryActionTypes.CREATE_ENTRY_SUCCESS ||
65+
action.type === EntryActionTypes.UPDATE_ENTRY_SUCCESS
66+
)
67+
)
68+
.subscribe((action) => {
6569
if (!action.payload.end_date) {
6670
this.store.dispatch(new LoadActiveEntry());
6771
this.store.dispatch(new entryActions.LoadEntriesSummary());
@@ -126,10 +130,36 @@ export class EntryFieldsComponent implements OnInit {
126130
this.entryForm.patchValue({ start_hour: this.newData.start_hour });
127131
return;
128132
}
133+
134+
this.getLastEntry();
135+
136+
const isFirstEntry = this.lastEntry !== undefined ? this.lastEntry.start_date : moment().add(-1, 'hours');
137+
const isEntryDateInLastStartDate = moment(newHourEntered).isBefore(isFirstEntry);
138+
if (isEntryDateInLastStartDate) {
139+
this.toastrService.error('You cannot start a time-entry before another time-entry');
140+
this.entryForm.patchValue({ start_hour: this.newData.start_hour });
141+
return;
142+
}
129143
this.entryForm.patchValue({ start_date: newHourEntered });
144+
this.dispatchEntries(newHourEntered);
145+
}
146+
147+
private dispatchEntries(newHourEntered) {
148+
const isFirstEntry = this.lastEntry !== undefined ? this.lastEntry.end_date : moment().add(-1, 'hours');
149+
const isInLastEntry = moment(newHourEntered).isBefore(isFirstEntry);
150+
if (isInLastEntry) {
151+
this.store.dispatch(new entryActions.UpdateEntry({ id: this.lastEntry.id, end_date: newHourEntered }));
152+
}
130153
this.store.dispatch(new entryActions.UpdateEntryRunning({ ...this.newData, ...this.entryForm.value }));
131154
}
132155

156+
private getLastEntry() {
157+
const lastEntry$ = this.store.pipe(select(getTimeEntriesDataSource));
158+
lastEntry$.subscribe((entry) => {
159+
this.lastEntry = entry.data[1];
160+
});
161+
}
162+
133163
onTechnologyAdded($event: string[]) {
134164
this.store.dispatch(new entryActions.UpdateEntryRunning({ ...this.newData, technologies: $event }));
135165
}

src/app/modules/time-clock/components/project-list-hover/project-list-hover.component.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ export class ProjectListHoverComponent implements OnInit, OnDestroy {
105105
} else {
106106
this.store.dispatch(new entryActions.SwitchTimeEntry(this.activeEntry.id, selectedProject));
107107
this.projectsForm.setValue( { project_id: `${customerName} - ${name}`, } );
108+
this.store.dispatch(new entryActions.LoadEntries(new Date().getMonth() + 1));
108109
}
109110
}
110111

0 commit comments

Comments
 (0)