+
Summary
+
+
+
+
Day
+ 4:22
+
+
+
Week
+ 14:25
+
+
+
Month
+ 49:32
+
+
+
-
Summary
-
-
-
-
Day
- 4:22
-
-
-
Week
- 14:25
-
-
-
Month
- 49:32
-
-
-
-
-
- {{ username }} clocked in at
- {{ activeTimeEntry?.start_date | date:'shortTime' }}
-
-
- {{ username }} you did not clock-in yet.
-
-
+
+
+
+ You clocked in at
+ {{ activeTimeEntry?.start_date | date:'shortTime' }}
+
+
+ Hi {{ username }}, please select a project to clock-in.
+
+
+
+
+
+
-
-
-
-
diff --git a/src/app/modules/time-clock/pages/time-clock.component.spec.ts b/src/app/modules/time-clock/pages/time-clock.component.spec.ts
index 8d33de27f..b4b17dc0d 100644
--- a/src/app/modules/time-clock/pages/time-clock.component.spec.ts
+++ b/src/app/modules/time-clock/pages/time-clock.component.spec.ts
@@ -1,5 +1,3 @@
-
-
import { EntryActionTypes, StopTimeEntryRunning } from './../store/entry.actions';
import { async, ComponentFixture, TestBed, inject } from '@angular/core/testing';
import { HttpClientTestingModule } from '@angular/common/http/testing';
@@ -19,7 +17,7 @@ describe('TimeClockComponent', () => {
let injectedToastrService;
const toastrService = {
- success: () => {}
+ success: () => {},
};
const state = {
projects: {
@@ -52,7 +50,8 @@ describe('TimeClockComponent', () => {
providers: [
AzureAdB2CService,
{ provide: ToastrService, useValue: toastrService },
- provideMockStore({ initialState: state })],
+ provideMockStore({ initialState: state }),
+ ],
}).compileComponents();
store = TestBed.inject(MockStore);
}));
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 01d685a66..2a90889b5 100644
--- a/src/app/modules/time-clock/services/entry.service.spec.ts
+++ b/src/app/modules/time-clock/services/entry.service.spec.ts
@@ -45,6 +45,15 @@ describe('EntryService', () => {
});
});
+ it('loads summary with get /summary', () => {
+ service.baseUrl = 'time-entries';
+
+ service.summary().subscribe((response) => {
+ const loadEntryRequest = httpMock.expectOne(`${service.baseUrl}/summary`);
+ expect(loadEntryRequest.request.method).toBe('GET');
+ });
+ });
+
it('loads all Entries', () => {
service.baseUrl = 'time-entries';
service.loadEntries().subscribe((response) => {
diff --git a/src/app/modules/time-clock/services/entry.service.ts b/src/app/modules/time-clock/services/entry.service.ts
index 460264eeb..1c3ec7515 100644
--- a/src/app/modules/time-clock/services/entry.service.ts
+++ b/src/app/modules/time-clock/services/entry.service.ts
@@ -1,3 +1,4 @@
+import { TimeEntriesSummary } from './../models/time.entry.summary';
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
@@ -38,4 +39,10 @@ export class EntryService {
const url = `${this.baseUrl}/${idEntry}/stop`;
return this.http.post(url, null);
}
+
+ summary(): Observable
{
+ const summaryUrl = `${this.baseUrl}/summary`;
+ return this.http.get(summaryUrl);
+ }
+
}
diff --git a/src/app/modules/time-clock/store/entry.actions.ts b/src/app/modules/time-clock/store/entry.actions.ts
index d6f745595..3342371bb 100644
--- a/src/app/modules/time-clock/store/entry.actions.ts
+++ b/src/app/modules/time-clock/store/entry.actions.ts
@@ -21,6 +21,8 @@ export enum EntryActionTypes {
STOP_TIME_ENTRY_RUNNING_SUCCESS = '[Entry] STOP_TIME_ENTRIES_RUNNING_SUCCESS',
STOP_TIME_ENTRY_RUNNING_FAILED = '[Entry] STOP_TIME_ENTRIES_RUNNING_FAILED',
DEFAULT_ENTRY = '[Entry] DEFAULT_ENTRY',
+ CLEAN_ENTRY_CREATE_ERROR = '[Entry] CLEAN_ENTRY_CREATE_ERROR',
+ CLEAN_ENTRY_UPDATE_ERROR = '[Entry] CLEAN_ENTRY_UPDATE_ERROR',
}
export class LoadActiveEntry implements Action {
@@ -120,6 +122,16 @@ export class StopTimeEntryRunningFail implements Action {
public readonly type = EntryActionTypes.STOP_TIME_ENTRY_RUNNING_FAILED;
constructor(public error: string) {}
}
+
+export class CleanEntryCreateError implements Action {
+ public readonly type = EntryActionTypes.CLEAN_ENTRY_CREATE_ERROR;
+ constructor(public error: boolean) {}
+}
+
+export class CleanEntryUpdateError implements Action {
+ public readonly type = EntryActionTypes.CLEAN_ENTRY_UPDATE_ERROR;
+ constructor(public error: boolean) {}
+}
export class DefaultEntry implements Action {
public readonly type = EntryActionTypes.DEFAULT_ENTRY;
}
@@ -143,4 +155,6 @@ export type EntryActions =
| StopTimeEntryRunning
| StopTimeEntryRunningSuccess
| StopTimeEntryRunningFail
+ | CleanEntryCreateError
+ | CleanEntryUpdateError
| DefaultEntry;
diff --git a/src/app/modules/time-clock/store/entry.effects.ts b/src/app/modules/time-clock/store/entry.effects.ts
index 99e46a683..03be222fd 100644
--- a/src/app/modules/time-clock/store/entry.effects.ts
+++ b/src/app/modules/time-clock/store/entry.effects.ts
@@ -2,13 +2,14 @@ import { Injectable } from '@angular/core';
import { ofType, Actions, Effect } from '@ngrx/effects';
import { Action } from '@ngrx/store';
import { of, Observable } from 'rxjs';
+import { ToastrService } from 'ngx-toastr';
import { catchError, map, mergeMap } from 'rxjs/operators';
import { EntryService } from '../services/entry.service';
import * as actions from './entry.actions';
@Injectable()
export class EntryEffects {
- constructor(private actions$: Actions, private entryService: EntryService) {}
+ constructor(private actions$: Actions, private entryService: EntryService, private toastrService: ToastrService) {}
@Effect()
loadActiveEntry$: Observable = this.actions$.pipe(
@@ -41,9 +42,13 @@ export class EntryEffects {
mergeMap((entry) =>
this.entryService.createEntry(entry).pipe(
map((entryData) => {
+ this.toastrService.success('Entry was saved successfully');
return new actions.CreateEntrySuccess(entryData);
}),
- catchError((error) => of(new actions.CreateEntryFail(error.error.message)))
+ catchError((error) => {
+ this.toastrService.error(error.error.message);
+ return of(new actions.CreateEntryFail(error.error.message));
+ })
)
)
);
@@ -67,9 +72,13 @@ export class EntryEffects {
mergeMap((project) =>
this.entryService.updateActiveEntry(project).pipe(
map((projectData) => {
+ this.toastrService.success('Entry was updated successfully');
return new actions.UpdateActiveEntrySuccess(projectData);
}),
- catchError((error) => of(new actions.UpdateActiveEntryFail(error)))
+ catchError((error) => {
+ this.toastrService.error(error.error.message);
+ return of(new actions.UpdateActiveEntryFail(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 9cc018f5d..e89f40f4c 100644
--- a/src/app/modules/time-clock/store/entry.reducer.spec.ts
+++ b/src/app/modules/time-clock/store/entry.reducer.spec.ts
@@ -3,7 +3,14 @@ import * as actions from './entry.actions';
import { entryReducer, EntryState } from './entry.reducer';
describe('entryReducer', () => {
- const initialState: EntryState = { active: null, entryList: [], isLoading: false, message: '' };
+ const initialState: EntryState = {
+ active: null,
+ entryList: [],
+ isLoading: false,
+ message: '',
+ createError: null,
+ updateError: null,
+ };
const entry: NewEntry = {
start_date: 'start-date',
description: 'description',
@@ -44,6 +51,7 @@ describe('entryReducer', () => {
const action = new actions.LoadActiveEntryFail('error');
const state = entryReducer(initialState, action);
expect(state.active).toBe(null);
+ expect(state.message).toEqual('Something went wrong fetching active entry!');
});
it('on LoadEntries, isLoading is true', () => {
@@ -107,8 +115,27 @@ describe('entryReducer', () => {
});
it('on DeleteEntrySuccess', () => {
- const action = new actions.DeleteEntry('id');
- const state = entryReducer(initialState, action);
+ const currentState = {
+ active: null,
+ entryList: [
+ {
+ project_id: '123',
+ comments: 'description',
+ technologies: ['angular', 'javascript'],
+ uri: 'uri',
+ id: 'id',
+ start_date: new Date(),
+ end_date: new Date(),
+ activity: 'activity',
+ },
+ ],
+ isLoading: false,
+ message: '',
+ createError: null,
+ updateError: null,
+ };
+ const action = new actions.DeleteEntrySuccess('id');
+ const state = entryReducer(currentState, action);
expect(state.entryList).toEqual([]);
});
@@ -128,16 +155,50 @@ describe('entryReducer', () => {
it('on UpdateActiveEntrySuccess, loading is false', () => {
const currentState: EntryState = {
active: null,
- entryList: [],
+ entryList: [
+ {
+ project_id: '123',
+ comments: 'description',
+ technologies: ['angular', 'javascript'],
+ uri: 'uri',
+ id: 'id',
+ start_date: new Date(),
+ end_date: new Date(),
+ activity: 'activity',
+ },
+ ],
isLoading: false,
message: '',
+ createError: null,
+ updateError: null,
+ };
+ const entryUpdated: Entry = {
+ id: 'id',
+ start_date: new Date(),
+ end_date: new Date(),
+ activity: '',
+ technologies: ['abc', 'abc'],
};
- const action = new actions.UpdateActiveEntrySuccess(newEntry);
+ const action = new actions.UpdateActiveEntrySuccess(entryUpdated);
const state = entryReducer(currentState, action);
expect(state.isLoading).toEqual(false);
});
+ it('on cleanEntryCreateError, createError to be null', () => {
+ const action = new actions.CleanEntryCreateError(null);
+ const state = entryReducer(initialState, action);
+
+ expect(state.createError).toBe(null);
+ });
+
+ it('on cleanEntryUpdateError, updateError to be null', () => {
+ const action = new actions.CleanEntryUpdateError(null);
+ const state = entryReducer(initialState, action);
+
+ expect(state.updateError).toBe(null);
+ });
+
it('on UpdateActiveEntryFail, active to be null', () => {
const action = new actions.UpdateActiveEntryFail('error');
const state = entryReducer(initialState, action);
diff --git a/src/app/modules/time-clock/store/entry.reducer.ts b/src/app/modules/time-clock/store/entry.reducer.ts
index dc6f185da..23e5327c6 100644
--- a/src/app/modules/time-clock/store/entry.reducer.ts
+++ b/src/app/modules/time-clock/store/entry.reducer.ts
@@ -6,6 +6,8 @@ export interface EntryState {
entryList: Entry[];
isLoading: boolean;
message: string;
+ createError: boolean;
+ updateError: boolean;
}
export const initialState = {
@@ -13,6 +15,8 @@ export const initialState = {
entryList: [],
isLoading: false,
message: '',
+ createError: null,
+ updateError: null
};
export const entryReducer = (state: EntryState = initialState, action: EntryActions) => {
@@ -74,6 +78,7 @@ export const entryReducer = (state: EntryState = initialState, action: EntryActi
active: action.payload,
entryList: [action.payload, ...state.entryList],
isLoading: false,
+ createError: false,
message: 'You clocked-in successfully',
};
}
@@ -83,6 +88,7 @@ export const entryReducer = (state: EntryState = initialState, action: EntryActi
...state,
isLoading: false,
message: action.error,
+ createError: true
};
}
@@ -127,6 +133,7 @@ export const entryReducer = (state: EntryState = initialState, action: EntryActi
...state,
entryList,
isLoading: false,
+ updateError: false,
};
}
@@ -135,6 +142,7 @@ export const entryReducer = (state: EntryState = initialState, action: EntryActi
...state,
active: null,
isLoading: false,
+ updateError: true
};
}
@@ -162,6 +170,20 @@ export const entryReducer = (state: EntryState = initialState, action: EntryActi
};
}
+ case EntryActionTypes.CLEAN_ENTRY_CREATE_ERROR: {
+ return {
+ ...state,
+ createError: null
+ };
+ }
+
+ case EntryActionTypes.CLEAN_ENTRY_UPDATE_ERROR: {
+ return {
+ ...state,
+ updateError: null
+ };
+ }
+
default: {
return state;
}
diff --git a/src/app/modules/time-clock/store/entry.selectors.ts b/src/app/modules/time-clock/store/entry.selectors.ts
index bbae7cdce..9282e5861 100644
--- a/src/app/modules/time-clock/store/entry.selectors.ts
+++ b/src/app/modules/time-clock/store/entry.selectors.ts
@@ -8,6 +8,13 @@ export const getActiveTimeEntry = createSelector(getEntryState, (state: EntrySta
return state.active;
});
+export const getCreateError = createSelector(getEntryState, (state: EntryState) => {
+ return state.createError;
+});
+
+export const getUpdateError = createSelector(getEntryState, (state: EntryState) => {
+ return state.updateError;
+});
export const getStatusMessage = createSelector(getEntryState, (state: EntryState) => state.message);
export const allEntries = createSelector(getEntryState, (state: EntryState) => state.entryList);
diff --git a/src/app/modules/time-entries/pages/time-entries.component.html b/src/app/modules/time-entries/pages/time-entries.component.html
index 2a4cb8839..e36952444 100644
--- a/src/app/modules/time-entries/pages/time-entries.component.html
+++ b/src/app/modules/time-entries/pages/time-entries.component.html
@@ -35,20 +35,20 @@
{{ entry.uri }} |
|
diff --git a/src/app/modules/time-entries/pages/time-entries.component.spec.ts b/src/app/modules/time-entries/pages/time-entries.component.spec.ts
index d4312a86e..96b6d4ff7 100644
--- a/src/app/modules/time-entries/pages/time-entries.component.spec.ts
+++ b/src/app/modules/time-entries/pages/time-entries.component.spec.ts
@@ -16,6 +16,7 @@ import { ProjectState } from '../../customer-management/components/projects/comp
import { getProjects } from '../../customer-management/components/projects/components/store/project.selectors';
import { EntryState } from '../../time-clock/store/entry.reducer';
import { allEntries } from '../../time-clock/store/entry.selectors';
+import * as entryActions from '../../time-clock/store/entry.actions';
describe('TimeEntriesComponent', () => {
type Merged = TechnologyState & ProjectState & EntryState;
@@ -151,6 +152,52 @@ describe('TimeEntriesComponent', () => {
expect(component.showModal).toBe(true);
});
+ it('should set entry and entryid to null', () => {
+ component.newEntry();
+ expect(component.entry).toBe(null);
+ expect(component.entryId).toBe(null);
+ });
+
+ it('should set entry and entryid to with data', () => {
+ component.entryList = [entry];
+ component.editEntry('entry_1');
+ expect(component.entry).toBe(entry);
+ expect(component.entryId).toBe('entry_1');
+ });
+
+ it('should update entry by id', () => {
+ const newEntry = {
+ project_id: 'projectId',
+ start_date: '',
+ description: 'description',
+ technologies: [],
+ uri: 'abc',
+ };
+ component.entryId = 'entry_1';
+ spyOn(store, 'dispatch');
+ component.saveEntry(newEntry);
+ expect(store.dispatch).toHaveBeenCalled();
+ });
+
+ it('should create new Entry', () => {
+ const newEntry = {
+ project_id: 'projectId',
+ start_date: '',
+ description: 'description',
+ technologies: [],
+ uri: 'abc',
+ };
+ spyOn(store, 'dispatch');
+ component.saveEntry(newEntry);
+ expect(store.dispatch).toHaveBeenCalledWith(new entryActions.CreateEntry(newEntry));
+ });
+
+ it('should delete Entry by id', () => {
+ spyOn(store, 'dispatch');
+ component.removeEntry('id');
+ expect(store.dispatch).toHaveBeenCalledWith(new entryActions.DeleteEntry('id'));
+ });
+
it('should get the entry List by Month', () => {
const month = 1;
component.entryList = [entry];