diff --git a/src/app/modules/time-clock/components/entry-fields/entry-fields.component.html b/src/app/modules/time-clock/components/entry-fields/entry-fields.component.html index 37764383b..fc29022fa 100644 --- a/src/app/modules/time-clock/components/entry-fields/entry-fields.component.html +++ b/src/app/modules/time-clock/components/entry-fields/entry-fields.component.html @@ -23,6 +23,25 @@ /> +
+ + +
+ { type Merged = TechnologyState & ProjectState; @@ -20,6 +23,10 @@ describe('EntryFieldsComponent', () => { let mockProjectsSelector; let entryForm; const actionSub: ActionsSubject = new ActionsSubject(); + const toastrServiceStub = { + error: (message?: string, title?: string, override?: Partial) => { }, + warning: (message?: string, title?: string, override?: Partial) => { } + }; const state = { projects: { @@ -59,12 +66,18 @@ describe('EntryFieldsComponent', () => { project_id: 'project-id-15', description: 'description for active entry', uri: 'abc', + start_date : moment().format('YYYY-MM-DD'), + start_hour : moment().format('HH:mm:ss'), }; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [EntryFieldsComponent], - providers: [provideMockStore({initialState: state}), { provide: ActionsSubject, useValue: actionSub }], + providers: [ + provideMockStore({initialState: state}), + { provide: ActionsSubject, useValue: actionSub }, + { provide: ToastrService, useValue: toastrServiceStub } + ], imports: [FormsModule, ReactiveFormsModule], }).compileComponents(); store = TestBed.inject(MockStore); @@ -97,10 +110,56 @@ describe('EntryFieldsComponent', () => { expect(component.entryForm.patchValue).toHaveBeenCalledTimes(1); expect(component.entryForm.patchValue).toHaveBeenCalledWith( - { description: entryDataForm.description, uri: entryDataForm.uri, activity_id: entryDataForm.activity_id }); + { + description: entryDataForm.description, + uri: entryDataForm.uri, + activity_id: entryDataForm.activity_id, + start_hour: formatDate(entry.start_date, 'HH:mm:ss', 'en') + } + ); expect(component.selectedTechnologies).toEqual([]); }); + it('displays error message when the date selected is in the future', () => { + component.newData = entry; + component.activeEntry = entry ; + component.setDataToUpdate(entry); + spyOn(toastrServiceStub, 'error'); + + const hourInTheFuture = moment().add(1, 'hours').format('HH:mm:ss'); + component.entryForm.patchValue({ start_hour : hourInTheFuture}); + component.onUpdateStartHour(); + + expect(toastrServiceStub.error).toHaveBeenCalled(); + }); + + it('If start hour is in the future, reset to initial start_date in form', () => { + component.newData = entry; + component.activeEntry = entry ; + component.setDataToUpdate(entry); + + const hourInTheFuture = moment().add(1, 'hours').format('HH:mm:ss'); + component.entryForm.patchValue({ start_hour : hourInTheFuture}); + + spyOn(component.entryForm, 'patchValue'); + component.onUpdateStartHour(); + + expect(component.entryForm.patchValue).toHaveBeenCalledWith( + { + start_hour: component.newData.start_hour + } + ); + }); + + it('when a start hour is updated, then dispatch UpdateActiveEntry', () => { + component.activeEntry = entry ; + component.setDataToUpdate(entry); + spyOn(store, 'dispatch'); + + component.onUpdateStartHour(); + expect(store.dispatch).toHaveBeenCalled(); + }); + it('when a technology is added, then dispatch UpdateActiveEntry', () => { const addedTechnologies = ['react']; spyOn(store, 'dispatch'); 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 f5d1206ed..c2aab444b 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 @@ -12,6 +12,10 @@ import { ActivityState, LoadActivities } from '../../../activities-management/st import * as entryActions from '../../store/entry.actions'; +import * as moment from 'moment'; +import { ToastrService } from 'ngx-toastr'; +import { formatDate } from '@angular/common'; + type Merged = TechnologyState & ProjectState & ActivityState; @Component({ @@ -26,56 +30,75 @@ export class EntryFieldsComponent implements OnInit { activeEntry; newData; - constructor(private formBuilder: FormBuilder, private store: Store, private actionsSubject$: ActionsSubject) { + constructor( + private formBuilder: FormBuilder, + private store: Store, + private actionsSubject$: ActionsSubject, + private toastrService: ToastrService + ) { this.entryForm = this.formBuilder.group({ description: '', uri: '', activity_id: '', + start_hour: '', + start_date: '', }); } ngOnInit(): void { this.store.dispatch(new LoadActivities()); - this.actionsSubject$.pipe( - filter((action: any) => (action.type === ActivityManagementActionTypes.LOAD_ACTIVITIES_SUCCESS)) - ).subscribe((action) => { - this.activities = action.payload; - this.store.dispatch(new LoadActiveEntry()); - }); - - this.actionsSubject$.pipe( - filter((action: any) => (action.type === EntryActionTypes.CREATE_ENTRY_SUCCESS)) - ).subscribe((action) => { - if (!action.payload.end_date) { + this.actionsSubject$ + .pipe(filter((action: any) => action.type === ActivityManagementActionTypes.LOAD_ACTIVITIES_SUCCESS)) + .subscribe((action) => { + this.activities = action.payload; this.store.dispatch(new LoadActiveEntry()); - } - }); + }); - this.actionsSubject$.pipe( - filter((action: any) => ( action.type === EntryActionTypes.LOAD_ACTIVE_ENTRY_SUCCESS )) - ).subscribe((action) => { - this.activeEntry = action.payload; - this.setDataToUpdate(this.activeEntry); - this.newData = { - id: this.activeEntry.id, - project_id: this.activeEntry.project_id, - uri: this.activeEntry.uri, - activity_id: this.activeEntry.activity_id, - }; - }); + this.actionsSubject$ + .pipe( + filter((action: any) => ( + action.type === EntryActionTypes.CREATE_ENTRY_SUCCESS || + action.type === EntryActionTypes.UPDATE_ENTRY_SUCCESS + )) + ).subscribe((action) => { + if (!action.payload.end_date) { + this.store.dispatch(new LoadActiveEntry()); + this.store.dispatch(new entryActions.LoadEntriesSummary()); + } + }); + + this.actionsSubject$ + .pipe(filter((action: any) => action.type === EntryActionTypes.LOAD_ACTIVE_ENTRY_SUCCESS)) + .subscribe((action) => { + this.activeEntry = action.payload; + this.setDataToUpdate(this.activeEntry); + this.newData = { + id: this.activeEntry.id, + project_id: this.activeEntry.project_id, + uri: this.activeEntry.uri, + activity_id: this.activeEntry.activity_id, + start_date: this.activeEntry.start_date, + start_hour: formatDate(this.activeEntry.start_date, 'HH:mm:ss', 'en'), + }; + }); } get activity_id() { return this.entryForm.get('activity_id'); } + get start_hour() { + return this.entryForm.get('start_hour'); + } + setDataToUpdate(entryData: NewEntry) { if (entryData) { this.entryForm.patchValue({ description: entryData.description, uri: entryData.uri, activity_id: entryData.activity_id, + start_hour: formatDate(entryData.start_date, 'HH:mm:ss', 'en'), }); if (entryData.technologies) { this.selectedTechnologies = entryData.technologies; @@ -93,6 +116,19 @@ export class EntryFieldsComponent implements OnInit { this.store.dispatch(new entryActions.UpdateEntryRunning({ ...this.newData, ...this.entryForm.value })); } + onUpdateStartHour() { + const startDate = formatDate(this.activeEntry.start_date, 'yyyy-MM-dd', 'en'); + const newHourEntered = new Date(`${startDate}T${this.entryForm.value.start_hour.trim()}`).toISOString(); + const isEntryDateInTheFuture = moment(newHourEntered).isAfter(moment()); + if (isEntryDateInTheFuture) { + this.toastrService.error('You cannot start a time-entry in the future'); + this.entryForm.patchValue({ start_hour: this.newData.start_hour }); + return; + } + this.entryForm.patchValue({ start_date: newHourEntered }); + this.store.dispatch(new entryActions.UpdateEntryRunning({ ...this.newData, ...this.entryForm.value })); + } + onTechnologyAdded($event: string[]) { this.store.dispatch(new entryActions.UpdateEntryRunning({ ...this.newData, technologies: $event })); }