Skip to content

Commit d540cc0

Browse files
authored
Merge pull request #410 from ioet/370-fix-switch-functionality
fix: #370 fix the issue when switching project on time-clock screen
2 parents 8afbfe7 + 6f5ac20 commit d540cc0

File tree

10 files changed

+110
-33
lines changed

10 files changed

+110
-33
lines changed

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

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { StopTimeEntryRunning } from './../../store/entry.actions';
1+
import { SwitchTimeEntry } from './../../store/entry.actions';
22
import { FormBuilder } from '@angular/forms';
33
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
44
import { provideMockStore, MockStore } from '@ngrx/store/testing';
@@ -67,7 +67,7 @@ describe('ProjectListHoverComponent', () => {
6767
expect(store.dispatch).toHaveBeenCalledWith(jasmine.any(CreateEntry));
6868
});
6969

70-
it('dispatchs a UpdateEntryRunning action on updateProject', () => {
70+
it('dispatch a UpdateEntryRunning action on updateProject', () => {
7171
component.activeEntry = { id: '123' };
7272
spyOn(store, 'dispatch');
7373

@@ -76,15 +76,13 @@ describe('ProjectListHoverComponent', () => {
7676
expect(store.dispatch).toHaveBeenCalledWith(new UpdateEntryRunning({ id: component.activeEntry.id, project_id: 1 }));
7777
});
7878

79-
it('stop activeEntry and clockIn on swith', () => {
80-
spyOn(component, 'clockIn');
79+
it('dispatch a SwitchTimeEntry action', () => {
8180
spyOn(store, 'dispatch');
8281
component.activeEntry = { id: '123' };
8382

8483
component.switch(1, 'customer', 'project');
8584

86-
expect(store.dispatch).toHaveBeenCalledWith(new StopTimeEntryRunning(component.activeEntry.id));
87-
expect(component.clockIn).toHaveBeenCalled();
85+
expect(store.dispatch).toHaveBeenCalledWith(new SwitchTimeEntry(component.activeEntry.id, 1));
8886
});
8987

9088
it('calls unsubscribe on ngDestroy', () => {

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,9 +88,8 @@ export class ProjectListHoverComponent implements OnInit, OnDestroy {
8888
}
8989

9090
switch(selectedProject, customerName, name) {
91-
this.store.dispatch(new entryActions.StopTimeEntryRunning(this.activeEntry.id));
92-
this.clockIn(selectedProject, customerName, name);
93-
this.store.dispatch(new entryActions.LoadActiveEntry());
91+
this.store.dispatch(new entryActions.SwitchTimeEntry(this.activeEntry.id, selectedProject));
92+
this.projectsForm.setValue( { project_id: `${customerName} - ${name}`, } );
9493
}
9594

9695
ngOnDestroy(): void {

src/app/modules/time-clock/services/entry.service.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@ describe('EntryService', () => {
5050

5151
it('loads summary with get /summary?time_offset=<time-offset>', () => {
5252
service.summary().subscribe();
53-
const time_offset = new Date().getTimezoneOffset();
54-
const loadEntryRequest = httpMock.expectOne(`${service.baseUrl}/summary?time_offset=${time_offset}`);
53+
const timeOffset = new Date().getTimezoneOffset();
54+
const loadEntryRequest = httpMock.expectOne(`${service.baseUrl}/summary?time_offset=${timeOffset}`);
5555
expect(loadEntryRequest.request.method).toBe('GET');
5656
});
5757

src/app/modules/time-clock/services/entry.service.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@ export class EntryService {
4646
}
4747

4848
summary(): Observable<TimeEntriesSummary> {
49-
const time_offset = new Date().getTimezoneOffset();
50-
const summaryUrl = `${this.baseUrl}/summary?time_offset=${time_offset}`;
49+
const timeOffset = new Date().getTimezoneOffset();
50+
const summaryUrl = `${this.baseUrl}/summary?time_offset=${timeOffset}`;
5151
return this.http.get<TimeEntriesSummary>(summaryUrl);
5252
}
5353

src/app/modules/time-clock/store/entry.actions.spec.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,17 @@ import { TimeEntriesTimeRange } from '../models/time-entries-time-range';
44

55
describe('Actions for Entries', () => {
66

7+
8+
it('SwitchTimeEntry type is EntryActionTypes.SWITCH_TIME_ENTRY', () => {
9+
const action = new actions.SwitchTimeEntry('entry-id', 'project-id');
10+
expect(action.type).toEqual(actions.EntryActionTypes.SWITCH_TIME_ENTRY);
11+
});
12+
13+
it('SwitchTimeEntryFail type is EntryActionTypes.SWITCH_TIME_ENTRY_FAIL', () => {
14+
const action = new actions.SwitchTimeEntryFail('error msg');
15+
expect(action.type).toEqual(actions.EntryActionTypes.SWITCH_TIME_ENTRY_FAIL);
16+
});
17+
718
it('LoadEntriesSummaryFail type is EntryActionTypes.LOAD_ENTRIES_SUMMARY_FAIL', () => {
819
const action = new actions.LoadEntriesSummaryFail();
920
expect(action.type).toEqual(actions.EntryActionTypes.LOAD_ENTRIES_SUMMARY_FAIL);
@@ -35,10 +46,7 @@ describe('Actions for Entries', () => {
3546
});
3647

3748
it('CreateEntrySuccess type is EntryActionTypes.CREATE_ENTRY_SUCCESS', () => {
38-
const createEntrySuccess = new actions.CreateEntrySuccess({
39-
project_id: '1',
40-
start_date: '2020-04-21T19:51:36.559000+00:00',
41-
});
49+
const createEntrySuccess = new actions.CreateEntrySuccess(null);
4250
expect(createEntrySuccess.type).toEqual(actions.EntryActionTypes.CREATE_ENTRY_SUCCESS);
4351
});
4452

src/app/modules/time-clock/store/entry.actions.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,18 @@ export enum EntryActionTypes {
3232
LOAD_ENTRIES_BY_TIME_RANGE = '[Entry] LOAD_ENTRIES_BY_TIME_RANGE',
3333
LOAD_ENTRIES_BY_TIME_RANGE_SUCCESS = '[Entry] LOAD_ENTRIES_BY_TIME_RANGE_SUCCESS',
3434
LOAD_ENTRIES_BY_TIME_RANGE_FAIL = '[Entry] LOAD_ENTRIES_BY_TIME_RANGE_FAIL',
35+
SWITCH_TIME_ENTRY = '[Entry] SWITCH_TIME_ENTRY',
36+
SWITCH_TIME_ENTRY_FAIL = '[Entry] SWITCH_TIME_ENTRY_FAIL',
37+
}
38+
39+
export class SwitchTimeEntry implements Action {
40+
public readonly type = EntryActionTypes.SWITCH_TIME_ENTRY;
41+
constructor(readonly idEntrySwitching: string, readonly idProjectSwitching) {}
42+
}
43+
44+
export class SwitchTimeEntryFail implements Action {
45+
public readonly type = EntryActionTypes.SWITCH_TIME_ENTRY_FAIL;
46+
constructor(readonly error: string) {}
3547
}
3648

3749
export class LoadEntriesSummary implements Action {
@@ -87,7 +99,7 @@ export class CreateEntry implements Action {
8799
export class CreateEntrySuccess implements Action {
88100
public readonly type = EntryActionTypes.CREATE_ENTRY_SUCCESS;
89101

90-
constructor(public payload: NewEntry) {}
102+
constructor(public payload: Entry) {}
91103
}
92104

93105
export class CreateEntryFail implements Action {
@@ -208,4 +220,6 @@ export type EntryActions =
208220
| DefaultEntry
209221
| LoadEntriesByTimeRange
210222
| LoadEntriesByTimeRangeSuccess
211-
| LoadEntriesByTimeRangeFail;
223+
| LoadEntriesByTimeRangeFail
224+
| SwitchTimeEntry
225+
| SwitchTimeEntryFail;

src/app/modules/time-clock/store/entry.effects.spec.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { HttpClientTestingModule } from '@angular/common/http/testing';
77
import { ToastrModule, ToastrService } from 'ngx-toastr';
88
import { Action } from '@ngrx/store';
99
import { DatePipe } from '@angular/common';
10-
import { EntryActionTypes } from './entry.actions';
10+
import { EntryActionTypes, SwitchTimeEntry } from './entry.actions';
1111
import { EntryService } from '../services/entry.service';
1212
import { TimeEntriesTimeRange } from '../models/time-entries-time-range';
1313
import * as moment from 'moment';
@@ -38,6 +38,17 @@ describe('TimeEntryActionEffects', () => {
3838
expect(effects).toBeTruthy();
3939
});
4040

41+
it('stop the active entry and return a CreateEntryAction', async () => {
42+
actions$ = of(new SwitchTimeEntry('entry-id', 'project-id'));
43+
const serviceSpy = spyOn(service, 'stopEntryRunning');
44+
serviceSpy.and.returnValue(of({}));
45+
46+
effects.switchEntryRunning$.subscribe(action => {
47+
expect(service.stopEntryRunning).toHaveBeenCalledWith('entry-id');
48+
expect(action.type).toBe(EntryActionTypes.CREATE_ENTRY);
49+
});
50+
});
51+
4152
it('returns an action with type LOAD_ENTRIES_SUMMARY_SUCCESS when the service returns a value', () => {
4253
actions$ = of({type: EntryActionTypes.LOAD_ENTRIES_SUMMARY});
4354
const serviceSpy = spyOn(service, 'summary');

src/app/modules/time-clock/store/entry.effects.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { Actions, Effect, ofType } from '@ngrx/effects';
44
import { Action } from '@ngrx/store';
55
import { Observable, of } from 'rxjs';
66
import { ToastrService } from 'ngx-toastr';
7-
import { catchError, map, mergeMap } from 'rxjs/operators';
7+
import { catchError, map, mergeMap, switchMap } from 'rxjs/operators';
88
import { EntryService } from '../services/entry.service';
99
import * as actions from './entry.actions';
1010

@@ -13,6 +13,22 @@ export class EntryEffects {
1313
constructor(private actions$: Actions, private entryService: EntryService, private toastrService: ToastrService) {
1414
}
1515

16+
@Effect()
17+
switchEntryRunning$: Observable<Action> = this.actions$.pipe(
18+
ofType(actions.EntryActionTypes.SWITCH_TIME_ENTRY),
19+
switchMap((action: actions.SwitchTimeEntry) =>
20+
this.entryService.stopEntryRunning(action.idEntrySwitching).pipe(
21+
map(() => {
22+
return new actions.CreateEntry({ project_id: action.idProjectSwitching, start_date: new Date().toISOString() });
23+
}),
24+
catchError((error) => {
25+
this.toastrService.warning(error.error.message);
26+
return of(new actions.StopTimeEntryRunningFail(error.error.message));
27+
})
28+
)
29+
)
30+
);
31+
1632
@Effect()
1733
loadEntriesSummary$: Observable<Action> = this.actions$.pipe(
1834
ofType(actions.EntryActionTypes.LOAD_ENTRIES_SUMMARY),
@@ -46,7 +62,7 @@ export class EntryEffects {
4662
} else {
4763
const endDate = new Date(activeEntry.start_date);
4864
endDate.setHours(23, 59, 59);
49-
return new actions.UpdateEntry({id: activeEntry.id, end_date: endDate.toISOString()});
65+
return new actions.UpdateEntry({ id: activeEntry.id, end_date: endDate.toISOString() });
5066
}
5167
}
5268
}),

src/app/modules/time-clock/store/entry.reducer.spec.ts

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -121,12 +121,34 @@ describe('entryReducer', () => {
121121
expect(state.isLoading).toEqual(true);
122122
});
123123

124-
it('on CreateEntrySuccess, message is updated', () => {
125-
const entryToCreate: NewEntry = {project_id: '1', start_date: '2020-04-21T19:51:36.559000+00:00'};
126-
const action = new actions.CreateEntrySuccess(entryToCreate);
124+
it('on CreateEntrySuccess, if end_date is null then it is the active entry', () => {
125+
const entryCreated: Entry = { ... entry };
126+
entryCreated.end_date = null;
127+
const action = new actions.CreateEntrySuccess(entryCreated);
128+
129+
const state = entryReducer(initialState, action);
130+
131+
expect(state.active).toBe(entryCreated);
132+
});
133+
134+
it('on CreateEntrySuccess, if end_date is undefined then it is the active entry', () => {
135+
const entryCreated: Entry = { ... entry };
136+
entryCreated.end_date = undefined;
137+
const action = new actions.CreateEntrySuccess(entryCreated);
138+
139+
const state = entryReducer(initialState, action);
140+
141+
expect(state.active).toBe(entryCreated);
142+
});
143+
144+
it('on CreateEntrySuccess, if end_date has a value then it the active field is not updated', () => {
145+
const activeEntry: Entry = { ... entry };
146+
initialState.active = activeEntry;
147+
const action = new actions.CreateEntrySuccess(entry);
148+
127149
const state = entryReducer(initialState, action);
128150

129-
expect(state.message).toEqual('You clocked-in successfully');
151+
expect(state.active).toBe(activeEntry);
130152
});
131153

132154
it('on CreateEntryFail, entryList equal []', () => {

src/app/modules/time-clock/store/entry.reducer.ts

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -98,13 +98,22 @@ export const entryReducer = (state: EntryState = initialState, action: EntryActi
9898
case EntryActionTypes.CREATE_ENTRY_SUCCESS: {
9999
const entryList = [action.payload, ...state.entryList];
100100
entryList.sort((a, b) => new Date(b.start_date).getTime() - new Date(a.start_date).getTime());
101-
return {
102-
...state,
103-
entryList,
104-
isLoading: false,
105-
createError: false,
106-
message: 'You clocked-in successfully',
107-
};
101+
if (action.payload.end_date === null || action.payload.end_date === undefined) {
102+
return {
103+
...state,
104+
active: action.payload,
105+
entryList,
106+
isLoading: false,
107+
createError: false,
108+
};
109+
} else {
110+
return {
111+
...state,
112+
entryList,
113+
isLoading: false,
114+
createError: false,
115+
};
116+
}
108117
}
109118

110119
case EntryActionTypes.CREATE_ENTRY_FAIL: {

0 commit comments

Comments
 (0)