diff --git a/src/app/modules/time-clock/components/entry-fields/entry-fields.component.spec.ts b/src/app/modules/time-clock/components/entry-fields/entry-fields.component.spec.ts index 1334c0f77..04b46a644 100644 --- a/src/app/modules/time-clock/components/entry-fields/entry-fields.component.spec.ts +++ b/src/app/modules/time-clock/components/entry-fields/entry-fields.component.spec.ts @@ -99,7 +99,7 @@ describe('EntryFieldsComponent', () => { it('should dispatch UpdateActiveEntry action #onSubmit', () => { spyOn(store, 'dispatch'); component.onSubmit(); - expect(store.dispatch).toHaveBeenCalledWith(new entryActions.UpdateActiveEntry(entry)); + expect(store.dispatch).toHaveBeenCalledWith(new entryActions.UpdateEntryRunning(entry)); }); it('when a technology is added, then dispatch UpdateActiveEntry', () => { diff --git a/src/app/modules/time-clock/components/entry-fields/entry-fields.component.ts b/src/app/modules/time-clock/components/entry-fields/entry-fields.component.ts index 1cc4b00d1..0b1e2f556 100644 --- a/src/app/modules/time-clock/components/entry-fields/entry-fields.component.ts +++ b/src/app/modules/time-clock/components/entry-fields/entry-fields.component.ts @@ -74,16 +74,16 @@ export class EntryFieldsComponent implements OnInit { } onSubmit() { - this.store.dispatch(new entryActions.UpdateActiveEntry({...this.newData, ...this.entryForm.value})); + this.store.dispatch(new entryActions.UpdateEntryRunning({...this.newData, ...this.entryForm.value})); } onTechnologyAdded($event: string[]) { - this.store.dispatch(new entryActions.UpdateActiveEntry({...this.newData, technologies: $event}) + this.store.dispatch(new entryActions.UpdateEntryRunning({...this.newData, technologies: $event}) ); } onTechnologyRemoved($event: string[]) { - this.store.dispatch(new entryActions.UpdateActiveEntry({...this.newData, technologies: $event})); + this.store.dispatch(new entryActions.UpdateEntryRunning({...this.newData, technologies: $event})); } } diff --git a/src/app/modules/time-clock/components/project-list-hover/project-list-hover.component.spec.ts b/src/app/modules/time-clock/components/project-list-hover/project-list-hover.component.spec.ts index 1baa0dcbc..1624e316c 100644 --- a/src/app/modules/time-clock/components/project-list-hover/project-list-hover.component.spec.ts +++ b/src/app/modules/time-clock/components/project-list-hover/project-list-hover.component.spec.ts @@ -7,7 +7,7 @@ import {ProjectListHoverComponent} from './project-list-hover.component'; import {ProjectState} from '../../../customer-management/components/projects/components/store/project.reducer'; import {getCustomerProjects} from '../../../customer-management/components/projects/components/store/project.selectors'; import {FilterProjectPipe} from '../../../shared/pipes'; -import {CreateEntry, UpdateActiveEntry} from '../../store/entry.actions'; +import {CreateEntry, UpdateEntryRunning} from '../../store/entry.actions'; import {AutocompleteLibModule} from 'angular-ng-autocomplete'; describe('ProjectListHoverComponent', () => { @@ -65,13 +65,13 @@ describe('ProjectListHoverComponent', () => { expect(store.dispatch).toHaveBeenCalledWith(jasmine.any(CreateEntry)); }); - it('dispatchs a UpdateEntry action when activeEntry is not null', () => { + it('dispatchs a UpdateEntryRunning action when activeEntry is not null', () => { const entry = {id: '123', project_id: 'p1', start_date: new Date().toISOString()}; component.activeEntry = entry; spyOn(store, 'dispatch'); component.clockIn(); - expect(store.dispatch).toHaveBeenCalledWith(jasmine.any(UpdateActiveEntry)); + expect(store.dispatch).toHaveBeenCalledWith(jasmine.any(UpdateEntryRunning)); }); }); diff --git a/src/app/modules/time-clock/components/project-list-hover/project-list-hover.component.ts b/src/app/modules/time-clock/components/project-list-hover/project-list-hover.component.ts index 62ffbae5f..763a87edc 100644 --- a/src/app/modules/time-clock/components/project-list-hover/project-list-hover.component.ts +++ b/src/app/modules/time-clock/components/project-list-hover/project-list-hover.component.ts @@ -56,13 +56,12 @@ export class ProjectListHoverComponent implements OnInit { const selectedProject = this.projectsForm.get('project_id').value; if (this.activeEntry) { const entry = { id: this.activeEntry.id, project_id: selectedProject }; - this.store.dispatch(new entryActions.UpdateActiveEntry(entry)); + this.store.dispatch(new entryActions.UpdateEntryRunning(entry)); } else { const newEntry = { project_id: selectedProject, start_date: new Date().toISOString() }; this.store.dispatch(new entryActions.CreateEntry(newEntry)); } this.store.dispatch(new entryActions.LoadEntries(new Date().getMonth() + 1 )); - this.loadActiveTimeEntry(); } } diff --git a/src/app/modules/time-clock/services/entry.service.spec.ts b/src/app/modules/time-clock/services/entry.service.spec.ts index 1cc54a25e..4e4eb1cee 100644 --- a/src/app/modules/time-clock/services/entry.service.spec.ts +++ b/src/app/modules/time-clock/services/entry.service.spec.ts @@ -66,7 +66,7 @@ describe('EntryService', () => { it('update an entry using PUT', () => { const updatedEntry = {foo: 'bar', id: 'id'}; - service.updateActiveEntry(updatedEntry).subscribe(); + service.updateEntry(updatedEntry).subscribe(); const updateEntryRequest = httpMock.expectOne(`${service.baseUrl}/id`); expect(updateEntryRequest.request.method).toBe('PUT'); diff --git a/src/app/modules/time-clock/services/entry.service.ts b/src/app/modules/time-clock/services/entry.service.ts index 68d3a113b..384ed6de9 100644 --- a/src/app/modules/time-clock/services/entry.service.ts +++ b/src/app/modules/time-clock/services/entry.service.ts @@ -30,7 +30,7 @@ export class EntryService { return this.http.post(this.baseUrl, entryData); } - updateActiveEntry(entryData): Observable { + updateEntry(entryData): Observable { const {id} = entryData; return this.http.put(`${this.baseUrl}/${id}`, entryData); } diff --git a/src/app/modules/time-clock/store/entry.actions.spec.ts b/src/app/modules/time-clock/store/entry.actions.spec.ts index 0a78946fb..e57647382 100644 --- a/src/app/modules/time-clock/store/entry.actions.spec.ts +++ b/src/app/modules/time-clock/store/entry.actions.spec.ts @@ -55,20 +55,31 @@ describe('Actions for Entries', () => { expect(deleteEntryFail.type).toEqual(actions.EntryActionTypes.DELETE_ENTRY_FAIL); }); - it('UpdateActiveEntrySuccess type is EntryActionTypes.UDPATE_ACTIVE_ENTRY_SUCCESS', () => { - const updateActiveEntrySuccess = new actions.UpdateActiveEntrySuccess({ + it('UpdateEntrySuccess type is EntryActionTypes.UDPATE_ENTRY_SUCCESS', () => { + const updateActiveEntrySuccess = new actions.UpdateEntrySuccess({ id: '1', start_date: new Date(), end_date: new Date(), activity_id: '', technologies: ['abc', 'abc'], }); - expect(updateActiveEntrySuccess.type).toEqual(actions.EntryActionTypes.UPDATE_ACTIVE_ENTRY_SUCCESS); + expect(updateActiveEntrySuccess.type).toEqual(actions.EntryActionTypes.UPDATE_ENTRY_SUCCESS); }); - it('UpdateActiveEntryFail type is EntryActionTypes.UDPATE_ACTIVE_ENTRY_FAIL', () => { - const updateActiveEntryFail = new actions.UpdateActiveEntryFail('error'); - expect(updateActiveEntryFail.type).toEqual(actions.EntryActionTypes.UPDATE_ACTIVE_ENTRY_FAIL); + it('UpdateEntryFail type is EntryActionTypes.UDPATE_ENTRY_FAIL', () => { + const updateActiveEntryFail = new actions.UpdateEntryFail('error'); + expect(updateActiveEntryFail.type).toEqual(actions.EntryActionTypes.UPDATE_ENTRY_FAIL); + }); + + it('UpdateActiveEntry type is EntryActionTypes.UDPATE_ENTRY_FAIL', () => { + const action = new actions.UpdateEntryRunning({ + id: '1', + start_date: new Date(), + end_date: new Date(), + activity_id: '', + technologies: ['abc', 'abc'], + }); + expect(action.type).toEqual(actions.EntryActionTypes.UPDATE_ENTRY_RUNNING); }); it('LoadEntriesByTimeRange type is EntryActionTypes.LOAD_ENTRIES_BY_TIME_RANGE', () => { diff --git a/src/app/modules/time-clock/store/entry.actions.ts b/src/app/modules/time-clock/store/entry.actions.ts index 3cf03c51e..9152af611 100644 --- a/src/app/modules/time-clock/store/entry.actions.ts +++ b/src/app/modules/time-clock/store/entry.actions.ts @@ -16,9 +16,10 @@ export enum EntryActionTypes { CREATE_ENTRY = '[Entry] CREATE_ENTRY', CREATE_ENTRY_SUCCESS = '[Entry] CREATE_ENTRY_SUCCESS', CREATE_ENTRY_FAIL = '[Entry] CREATE_ENTRY_FAIL', - UPDATE_ACTIVE_ENTRY = '[Entry] UPDATE_ACTIVE_ENTRY', - UPDATE_ACTIVE_ENTRY_SUCCESS = '[Entry] UPDATE_ACTIVE_ENTRY_SUCCESS', - UPDATE_ACTIVE_ENTRY_FAIL = '[Entry] UPDATE_ACTIVE_ENTRY_FAIL', + UPDATE_ENTRY_RUNNING = '[Entry] UPDATE_ENTRY_RUNNING', + UPDATE_ENTRY = '[Entry] UPDATE_ENTRY', + UPDATE_ENTRY_SUCCESS = '[Entry] UPDATE_ENTRY_SUCCESS', + UPDATE_ENTRY_FAIL = '[Entry] UPDATE_ENTRY_FAIL', DELETE_ENTRY = '[Entry] DELETE_ENTRY', DELETE_ENTRY_SUCCESS = '[Entry] DELETE_ENTRY_SUCCESS', DELETE_ENTRY_FAIL = '[Entry] DELETE_ENTRY_FAIL', @@ -112,20 +113,26 @@ export class DeleteEntryFail implements Action { constructor(public error: string) {} } -export class UpdateActiveEntry implements Action { - public readonly type = EntryActionTypes.UPDATE_ACTIVE_ENTRY; +export class UpdateEntryRunning implements Action { + public readonly type = EntryActionTypes.UPDATE_ENTRY_RUNNING; constructor(public payload) {} } -export class UpdateActiveEntrySuccess implements Action { - public readonly type = EntryActionTypes.UPDATE_ACTIVE_ENTRY_SUCCESS; +export class UpdateEntry implements Action { + public readonly type = EntryActionTypes.UPDATE_ENTRY; + + constructor(public payload) {} +} + +export class UpdateEntrySuccess implements Action { + public readonly type = EntryActionTypes.UPDATE_ENTRY_SUCCESS; constructor(public payload: Entry) {} } -export class UpdateActiveEntryFail implements Action { - public readonly type = EntryActionTypes.UPDATE_ACTIVE_ENTRY_FAIL; +export class UpdateEntryFail implements Action { + public readonly type = EntryActionTypes.UPDATE_ENTRY_FAIL; constructor(public error: string) {} } @@ -186,9 +193,10 @@ export type EntryActions = | CreateEntry | CreateEntrySuccess | CreateEntryFail - | UpdateActiveEntry - | UpdateActiveEntrySuccess - | UpdateActiveEntryFail + | UpdateEntryRunning + | UpdateEntry + | UpdateEntrySuccess + | UpdateEntryFail | DeleteEntry | DeleteEntrySuccess | DeleteEntryFail diff --git a/src/app/modules/time-clock/store/entry.effects.spec.ts b/src/app/modules/time-clock/store/entry.effects.spec.ts index 0595ca556..ea13cb1a1 100644 --- a/src/app/modules/time-clock/store/entry.effects.spec.ts +++ b/src/app/modules/time-clock/store/entry.effects.spec.ts @@ -1,9 +1,10 @@ +import { INFO_SAVED_SUCCESSFULLY } from './../../shared/messages'; import {TestBed} from '@angular/core/testing'; import {provideMockActions} from '@ngrx/effects/testing'; import {EntryEffects} from './entry.effects'; import {Observable, of, throwError} from 'rxjs'; import {HttpClientTestingModule} from '@angular/common/http/testing'; -import {ToastrModule} from 'ngx-toastr'; +import { ToastrModule, ToastrService } from 'ngx-toastr'; import {Action} from '@ngrx/store'; import {DatePipe} from '@angular/common'; import {EntryActionTypes} from './entry.actions'; @@ -15,6 +16,7 @@ describe('TimeEntryActionEffects', () => { let actions$: Observable; let effects: EntryEffects; let service; + let toastrService; beforeEach(() => { TestBed.configureTestingModule({ @@ -28,6 +30,7 @@ describe('TimeEntryActionEffects', () => { }); effects = TestBed.inject(EntryEffects); service = TestBed.inject(EntryService); + toastrService = TestBed.inject(ToastrService); }); it('should be created', async () => { @@ -95,7 +98,27 @@ describe('TimeEntryActionEffects', () => { serviceSpy.and.returnValue(of(activeEntry)); effects.loadActiveEntry$.subscribe(action => { - expect(action.type).toEqual(EntryActionTypes.UPDATE_ACTIVE_ENTRY); + expect(action.type).toEqual(EntryActionTypes.UPDATE_ENTRY); + }); + }); + + it('display a success message on UPDATE_ENTRY', async () => { + const activeEntry = {}; + actions$ = of({type: EntryActionTypes.UPDATE_ENTRY, activeEntry}); + spyOn(toastrService, 'success'); + + effects.updateEntry$.subscribe(action => { + expect(toastrService.success).toHaveBeenCalledWith(INFO_SAVED_SUCCESSFULLY); + }); + }); + + it('does not display any message on UPDATE_ENTRY_RUNNING', async () => { + const activeEntry = {}; + actions$ = of({type: EntryActionTypes.UPDATE_ENTRY_RUNNING, activeEntry}); + spyOn(toastrService, 'success'); + + effects.updateEntry$.subscribe(action => { + expect(toastrService.success).toHaveBeenCalledTimes(0); }); }); diff --git a/src/app/modules/time-clock/store/entry.effects.ts b/src/app/modules/time-clock/store/entry.effects.ts index aae5ae452..3bf06e09e 100644 --- a/src/app/modules/time-clock/store/entry.effects.ts +++ b/src/app/modules/time-clock/store/entry.effects.ts @@ -46,7 +46,7 @@ export class EntryEffects { } else { const endDate = new Date(activeEntry.start_date); endDate.setHours(23, 59, 59); - return new actions.UpdateActiveEntry({id: activeEntry.id, end_date: endDate.toISOString()}); + return new actions.UpdateEntry({id: activeEntry.id, end_date: endDate.toISOString()}); } } }), @@ -113,18 +113,35 @@ export class EntryEffects { ); @Effect() - updateActiveEntry$: Observable = this.actions$.pipe( - ofType(actions.EntryActionTypes.UPDATE_ACTIVE_ENTRY), - map((action: actions.UpdateActiveEntry) => action.payload), + updateEntry$: Observable = this.actions$.pipe( + ofType(actions.EntryActionTypes.UPDATE_ENTRY), + map((action: actions.UpdateEntry) => action.payload), mergeMap((entry) => - this.entryService.updateActiveEntry(entry).pipe( + this.entryService.updateEntry(entry).pipe( map((entryResponse) => { this.toastrService.success(INFO_SAVED_SUCCESSFULLY); - return new actions.UpdateActiveEntrySuccess(entryResponse); + return new actions.UpdateEntrySuccess(entryResponse); }), catchError((error) => { this.toastrService.error(error.error.message); - return of(new actions.UpdateActiveEntryFail(error)); + return of(new actions.UpdateEntryFail(error)); + }) + ) + ) + ); + + @Effect() + updateEntryRunning$: Observable = this.actions$.pipe( + ofType(actions.EntryActionTypes.UPDATE_ENTRY_RUNNING), + map((action: actions.UpdateEntry) => action.payload), + mergeMap((entry) => + this.entryService.updateEntry(entry).pipe( + map((entryResponse) => { + return new actions.UpdateEntrySuccess(entryResponse); + }), + catchError((error) => { + this.toastrService.error(error.error.message); + return of(new actions.UpdateEntryFail(error)); }) ) ) diff --git a/src/app/modules/time-clock/store/entry.reducer.spec.ts b/src/app/modules/time-clock/store/entry.reducer.spec.ts index f2aa88e48..90b6575f8 100644 --- a/src/app/modules/time-clock/store/entry.reducer.spec.ts +++ b/src/app/modules/time-clock/store/entry.reducer.spec.ts @@ -158,14 +158,14 @@ describe('entryReducer', () => { }); it('on UpdateActiveEntry, isLoading is true', () => { - const action = new actions.UpdateActiveEntry(newEntry); + const action = new actions.UpdateEntry(newEntry); const state = entryReducer(initialState, action); expect(state.isLoading).toEqual(true); }); it('on UpdateActiveEntrySuccess, loading is false', () => { - const action = new actions.UpdateActiveEntrySuccess(entry); + const action = new actions.UpdateEntrySuccess(entry); const state = entryReducer(initialState, action); @@ -187,7 +187,7 @@ describe('entryReducer', () => { }); it('on UpdateActiveEntryFail, active to be null', () => { - const action = new actions.UpdateActiveEntryFail('error'); + const action = new actions.UpdateEntryFail('error'); const state = entryReducer(initialState, action); expect(state.active).toBe(null); diff --git a/src/app/modules/time-clock/store/entry.reducer.ts b/src/app/modules/time-clock/store/entry.reducer.ts index afc898ca0..6f95c854b 100644 --- a/src/app/modules/time-clock/store/entry.reducer.ts +++ b/src/app/modules/time-clock/store/entry.reducer.ts @@ -142,14 +142,14 @@ export const entryReducer = (state: EntryState = initialState, action: EntryActi }; } - case EntryActionTypes.UPDATE_ACTIVE_ENTRY: { + case EntryActionTypes.UPDATE_ENTRY: { return { ...state, isLoading: true, }; } - case EntryActionTypes.UPDATE_ACTIVE_ENTRY_SUCCESS: { + case EntryActionTypes.UPDATE_ENTRY_SUCCESS: { const entryList = [...state.entryList]; const index = entryList.findIndex((entry) => entry.id === action.payload.id); entryList[index] = action.payload; @@ -161,7 +161,7 @@ export const entryReducer = (state: EntryState = initialState, action: EntryActi }; } - case EntryActionTypes.UPDATE_ACTIVE_ENTRY_FAIL: { + case EntryActionTypes.UPDATE_ENTRY_FAIL: { return { ...state, active: null, diff --git a/src/app/modules/time-entries/pages/time-entries.component.ts b/src/app/modules/time-entries/pages/time-entries.component.ts index 2628d24e8..08b943cf0 100644 --- a/src/app/modules/time-entries/pages/time-entries.component.ts +++ b/src/app/modules/time-entries/pages/time-entries.component.ts @@ -61,7 +61,7 @@ export class TimeEntriesComponent implements OnInit { doSave(entry) { if (this.entryId) { entry.id = this.entryId; - this.store.dispatch(new entryActions.UpdateActiveEntry(entry)); + this.store.dispatch(new entryActions.UpdateEntry(entry)); } else { this.store.dispatch(new entryActions.CreateEntry(entry)); }