Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ export class DetailsFieldsComponent implements OnChanges, OnInit {
project_id: this.entryToEdit.project_id,
activity_id: this.entryToEdit.activity_id,
description: this.entryToEdit.description,
start_date: formatDate(get(this.entryToEdit, 'start_date', '') ,DATE_FORMAT, 'en'),
start_date: formatDate(get(this.entryToEdit, 'start_date', ''), DATE_FORMAT, 'en'),
end_date: formatDate(get(this.entryToEdit, 'end_date'), DATE_FORMAT, 'en'),
start_hour: formatDate(get(this.entryToEdit, 'start_date', '00:00'), 'HH:mm', 'en'),
end_hour: formatDate(get(this.entryToEdit, 'end_date', '00:00'), 'HH:mm', 'en'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -262,19 +262,16 @@ describe('EntryFieldsComponent', () => {
expect(component.lastEntry).toBe(state.entries.timeEntriesDataSource.data[1]);
}));

it('When start_time is updated for a time entry. UpdateEntry and UpdateEntryRuning actions are dispatched', () => {
it('When start_time is updated for a time entry. UpdateCurrentOrLastEntry action is dispatched', () => {
component.activeEntry = entry ;
component.setDataToUpdate(entry);
const lastEntry = state.entries.timeEntriesDataSource.data[1];
const updatedTime = moment().subtract(4, 'hours').format('HH:mm');
const lastStartHourEntryEnteredTest = new Date(`${lastDate}T${updatedTime.trim()}`).toISOString();
component.entryForm.patchValue({ start_hour : updatedTime});
spyOn(store, 'dispatch');

component.onUpdateStartHour();

expect(store.dispatch).toHaveBeenCalledTimes(2);
expect(store.dispatch).toHaveBeenCalledWith(new UpdateEntry({id: lastEntry.id, end_date: lastStartHourEntryEnteredTest}));
expect(store.dispatch).toHaveBeenCalledTimes(1);
});

it('when a technology is added, then dispatch UpdateActiveEntry', () => {
Expand Down Expand Up @@ -370,7 +367,7 @@ describe('EntryFieldsComponent', () => {
expect(store.dispatch).toHaveBeenCalledWith(new LoadActiveEntry());
});

it('when entry has an end_date then nothing is dispatched', () => {
it('When update current or last entry then the actions updateEntryRunning, LoadEntries and LoadEntriSummary will be dispatched', () => {
spyOn(store, 'dispatch');

const actionSubject = TestBed.inject(ActionsSubject) as ActionsSubject;
Expand All @@ -381,7 +378,7 @@ describe('EntryFieldsComponent', () => {

actionSubject.next(action);

expect(store.dispatch).toHaveBeenCalledTimes(0);
expect(store.dispatch).toHaveBeenCalledTimes(3);
});

it('activeEntry is populated using the payload of LOAD_ACTIVE_ENTRY_SUCCESS', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ export class EntryFieldsComponent implements OnInit {
if (!action.payload.end_date) {
this.store.dispatch(new LoadActiveEntry());
this.store.dispatch(new entryActions.LoadEntriesSummary());
} else {
this.store.dispatch(new entryActions.UpdateEntryRunning({ ...this.newData, ...this.entryForm.value }));
this.store.dispatch(new LoadActiveEntry());
this.store.dispatch(new entryActions.LoadEntriesSummary());
}
});

Expand Down Expand Up @@ -143,31 +147,22 @@ export class EntryFieldsComponent implements OnInit {
return;
}
this.entryForm.patchValue({ start_date: newHourEntered });
this.dispatchEntries(newHourEntered);
this.store.dispatch(new entryActions.UpdateCurrentOrLastEntry({ ...this.newData, ...this.entryForm.value }));
this.showTimeInbuttons = false;
}

private dispatchEntries(newHourEntered) {
const lastEntryEndDate = get(this.lastEntry, 'end_date', moment().subtract(1, 'hours'));
const isInLastEntry = moment(newHourEntered).isBefore(lastEntryEndDate);
if (isInLastEntry) {
this.store.dispatch(new entryActions.UpdateEntry({ id: this.lastEntry.id, end_date: newHourEntered }));
}
this.store.dispatch(new entryActions.UpdateEntryRunning({ ...this.newData, ...this.entryForm.value }));
}

private getLastEntry() {
const lastEntry$ = this.store.pipe(select(getTimeEntriesDataSource));
lastEntry$.subscribe((entry) => {
this.lastEntry = entry.data[1];
});
}

activeTimeInButtons(){
activeTimeInButtons() {
this.showTimeInbuttons = true;
}

cancelTimeInUpdate(){
cancelTimeInUpdate() {
this.entryForm.patchValue({ start_hour: this.newData.start_hour });
this.showTimeInbuttons = false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,6 @@ export class ProjectListHoverComponent implements OnInit, OnDestroy {
} else {
this.store.dispatch(new entryActions.SwitchTimeEntry(this.activeEntry.id, selectedProject));
this.projectsForm.setValue( { project_id: `${customerName} - ${name}`, } );
this.store.dispatch(new entryActions.LoadEntries(new Date().getMonth() + 1));
}
}

Expand Down
10 changes: 10 additions & 0 deletions src/app/modules/time-clock/store/entry.actions.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,4 +126,14 @@ describe('Actions for Entries', () => {
const action = new actions.RestartEntryFail('error');
expect(action.type).toEqual(actions.EntryActionTypes.RESTART_ENTRY_FAIL);
});

it('UpdateCurrentOrLastEntry type is EntryActionTypes.UPDATE_CURRENT_OR_LAST_ENTRY', () => {
const action = new actions.UpdateCurrentOrLastEntry(entry);
expect(action.type).toEqual(actions.EntryActionTypes.UPDATE_CURRENT_OR_LAST_ENTRY);
});

it('UpdateCurrentOrLastEntryFail type is EntryActionTypes.UPDATE_CURRENT_OR_LAST_ENTRY_FAIL', () => {
const action = new actions.UpdateCurrentOrLastEntryFail('error');
expect(action.type).toEqual(actions.EntryActionTypes.UPDATE_CURRENT_OR_LAST_ENTRY_FAIL);
});
});
18 changes: 17 additions & 1 deletion src/app/modules/time-clock/store/entry.actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ export enum EntryActionTypes {
UPDATE_ENTRY_RUNNING = '[Entry] UPDATE_ENTRY_RUNNING',
UPDATE_ENTRY = '[Entry] UPDATE_ENTRY',
UPDATE_ENTRY_SUCCESS = '[Entry] UPDATE_ENTRY_SUCCESS',
UPDATE_CURRENT_OR_LAST_ENTRY = '[Entry] UPDATE_CURRENT_OR_LAST_ENTRY',
UPDATE_CURRENT_OR_LAST_ENTRY_FAIL = '[Entry] UPDATE_CURRENT_OR_LAST_ENTRY_FAIL',
UPDATE_ENTRY_FAIL = '[Entry] UPDATE_ENTRY_FAIL',
DELETE_ENTRY = '[Entry] DELETE_ENTRY',
DELETE_ENTRY_SUCCESS = '[Entry] DELETE_ENTRY_SUCCESS',
Expand Down Expand Up @@ -183,6 +185,18 @@ export class UpdateEntryFail implements Action {
}
}

export class UpdateCurrentOrLastEntry implements Action {
public readonly type = EntryActionTypes.UPDATE_CURRENT_OR_LAST_ENTRY;

constructor(public payload) {
}
}
export class UpdateCurrentOrLastEntryFail implements Action {
public readonly type = EntryActionTypes.UPDATE_CURRENT_OR_LAST_ENTRY_FAIL;

constructor(public error: string) {
}
}
export class StopTimeEntryRunning implements Action {
public readonly type = EntryActionTypes.STOP_TIME_ENTRY_RUNNING;

Expand Down Expand Up @@ -296,4 +310,6 @@ export type EntryActions =
| SwitchTimeEntryFail
| RestartEntry
| RestartEntrySuccess
| RestartEntryFail;
| RestartEntryFail
| UpdateCurrentOrLastEntry
| UpdateCurrentOrLastEntryFail;
43 changes: 43 additions & 0 deletions src/app/modules/time-clock/store/entry.effects.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,19 @@ describe('TimeEntryActionEffects', () => {
let toastrService;
const entry: Entry = { project_id: 'p-id', start_date: new Date(), id: 'id' };

const dateTest = moment().format('YYYY-MM-DD');
const endHourTest = moment().subtract(5, 'hours').format('HH:mm:ss');
const startHourTest = moment().subtract(3, 'hours').format('HH:mm:ss');
const endDateTest = new Date(`${dateTest}T${endHourTest.trim()}`);
const startDateTest = new Date(`${dateTest}T${startHourTest.trim()}`);

const entryUpdate = {
id: 'id',
project_id: 'p-id',
start_date : startDateTest,
start_hour : moment().subtract(1, 'hours').format('HH:mm'),
};

beforeEach(() => {
TestBed.configureTestingModule({
providers: [
Expand Down Expand Up @@ -339,4 +352,34 @@ describe('TimeEntryActionEffects', () => {
expect(action.type).toEqual(EntryActionTypes.STOP_TIME_ENTRY_RUNNING_FAILED);
});
});

it('should update last entry when UPDATE_CURRENT_OR_LAST_ENTRY is executed', async () => {
actions$ = of({ type: EntryActionTypes.UPDATE_CURRENT_OR_LAST_ENTRY, payload: entry });
spyOn(service, 'loadEntries').and.returnValue(of([entry, entry]));

effects.updateCurrentOrLastEntry$.subscribe(action => {
expect(action.type).toEqual(EntryActionTypes.UPDATE_ENTRY);
});
});

it('should update current entry when UPDATE_CURRENT_OR_LAST_ENTRY is executed', async () => {
const lastEntry: Entry = { project_id: 'p-id', start_date: new Date(), id: 'id', end_date: endDateTest};
actions$ = of({ type: EntryActionTypes.UPDATE_CURRENT_OR_LAST_ENTRY, payload: entryUpdate });
spyOn(service, 'loadEntries').and.returnValue(of([lastEntry, lastEntry]));
spyOn(toastrService, 'success');

effects.updateCurrentOrLastEntry$.subscribe(action => {
expect(toastrService.success).toHaveBeenCalledWith('You change the time-in successfully');
expect(action.type).toEqual(EntryActionTypes.UPDATE_ENTRY_RUNNING);
});
});

it('action type is UPDATE_CURRENT_OR_LAST_ENTRY_FAIL when service fail in execution', async () => {
actions$ = of({ type: EntryActionTypes.UPDATE_CURRENT_OR_LAST_ENTRY, payload: entry });
spyOn(service, 'loadEntries').and.returnValue(throwError({ error: { message: 'fail!' } }));

effects.updateCurrentOrLastEntry$.subscribe((action) => {
expect(action.type).toEqual(EntryActionTypes.UPDATE_CURRENT_OR_LAST_ENTRY_FAIL);
});
});
});
25 changes: 25 additions & 0 deletions src/app/modules/time-clock/store/entry.effects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Observable, of } from 'rxjs';
import { catchError, map, mergeMap, switchMap } from 'rxjs/operators';
import { EntryService } from '../services/entry.service';
import * as actions from './entry.actions';
import * as moment from 'moment';

@Injectable()
export class EntryEffects {
Expand Down Expand Up @@ -219,6 +220,30 @@ export class EntryEffects {
)
);

@Effect()
updateCurrentOrLastEntry$: Observable<Action> = this.actions$.pipe(
ofType(actions.EntryActionTypes.UPDATE_CURRENT_OR_LAST_ENTRY),
map((action: actions.UpdateCurrentOrLastEntry) => action.payload),
switchMap((entry) =>
this.entryService.loadEntries(new Date().getMonth() + 1).pipe(
map((entries) => {
const lastEntry = entries[1];
const isStartTimeInLastEntry = moment(entry.start_date).isBefore(lastEntry.end_date);
if (isStartTimeInLastEntry) {
return new actions.UpdateEntry({ id: lastEntry.id, end_date: entry.start_date });
} else {
this.toastrService.success('You change the time-in successfully');
return new actions.UpdateEntryRunning(entry);
}
}),
catchError((error) => {
this.toastrService.error(error.error.message);
return of(new actions.UpdateCurrentOrLastEntryFail('error'));
})
)
)
);

@Effect()
loadEntriesByTimeRange$: Observable<Action> = this.actions$.pipe(
ofType(actions.EntryActionTypes.LOAD_ENTRIES_BY_TIME_RANGE),
Expand Down
15 changes: 15 additions & 0 deletions src/app/modules/time-clock/store/entry.reducer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,21 @@ describe('entryReducer', () => {
expect(state.isLoading).toEqual(true);
});

it('on UpdateCurrentOrLastEntry, isLoading is true', () => {
const action = new actions.UpdateCurrentOrLastEntry(newEntry);
const state = entryReducer(initialState, action);

expect(state.isLoading).toEqual(true);
});

it('on UpdateCurrentOrLastEntryFail, isLoading is false and give a message', () => {
const action = new actions.UpdateCurrentOrLastEntryFail('fail');
const state = entryReducer(initialState, action);

expect(state.isLoading).toEqual(false);
expect(state.message).toEqual('Update Current or Last Entry Fail');
});

it('on UpdateActiveEntrySuccess, loading is false', () => {
const action = new actions.UpdateEntrySuccess(entry);

Expand Down
15 changes: 15 additions & 0 deletions src/app/modules/time-clock/store/entry.reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,21 @@ export const entryReducer = (state: EntryState = initialState, action: EntryActi
};
}

case EntryActionTypes.UPDATE_CURRENT_OR_LAST_ENTRY: {
return {
...state,
isLoading: true,
};
}

case EntryActionTypes.UPDATE_CURRENT_OR_LAST_ENTRY_FAIL: {
return {
...state,
isLoading: false,
message: 'Update Current or Last Entry Fail',
};
}

case EntryActionTypes.STOP_TIME_ENTRY_RUNNING: {
return {
...state,
Expand Down
3 changes: 2 additions & 1 deletion src/app/modules/time-entries/pages/time-entries.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,8 @@ export class TimeEntriesComponent implements OnInit, OnDestroy {
const isEditingEntryEqualToActiveEntry = this.entryId === this.activeTimeEntry.id;
const isStartDateGreaterThanActiveEntry = startDateAsLocalDate > activeEntryAsLocalDate;
const isEndDateGreaterThanActiveEntry = endDateAsLocalDate > activeEntryAsLocalDate;
if(!isEditingEntryEqualToActiveEntry && (isStartDateGreaterThanActiveEntry || isEndDateGreaterThanActiveEntry)){
const isTimeEntryOverlapping = isStartDateGreaterThanActiveEntry || isEndDateGreaterThanActiveEntry;
if (!isEditingEntryEqualToActiveEntry && isTimeEntryOverlapping) {
this.toastrService.error('You are on the clock and this entry overlaps it, try with earlier times.');
} else {
this.doSave(event);
Expand Down