- Time in
+ Time in
-
diff --git a/src/app/modules/shared/components/details-fields/details-fields.component.spec.ts b/src/app/modules/shared/components/details-fields/details-fields.component.spec.ts
index b19cdd322..b307d5632 100644
--- a/src/app/modules/shared/components/details-fields/details-fields.component.spec.ts
+++ b/src/app/modules/shared/components/details-fields/details-fields.component.spec.ts
@@ -2,7 +2,7 @@ import { TechnologiesComponent } from './../technologies/technologies.component'
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { provideMockStore, MockStore } from '@ngrx/store/testing';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
-import { formatDate } from '@angular/common';
+import { DatePipe, formatDate } from '@angular/common';
import { TechnologyState } from '../../store/technology.reducers';
import { allTechnologies } from '../../store/technology.selectors';
@@ -22,21 +22,23 @@ describe('DetailsFieldsComponent', () => {
let mockProjectsSelector;
let mockEntriesUpdateErrorSelector;
let mockEntriesCreateErrorSelector;
+ let entryToEdit;
+ let formValues;
const state = {
projects: {
- projects: [{ id: 'id', name: 'name', project_type_id: '' }],
- customerProjects: [{ id: 'id', name: 'name', description: 'description', project_type_id: '123' }],
+ projects: [{id: 'id', name: 'name', project_type_id: ''}],
+ customerProjects: [{id: 'id', name: 'name', description: 'description', project_type_id: '123'}],
isLoading: false,
message: '',
projectToEdit: undefined,
},
technologies: {
- technologyList: { items: [{ name: 'java' }] },
+ technologyList: {items: [{name: 'java'}]},
isLoading: false,
},
activities: {
- data: [{ id: 'fc5fab41-a21e-4155-9d05-511b956ebd05', tenant_id: 'ioet', deleted: null, name: 'abc' }],
+ data: [{id: 'fc5fab41-a21e-4155-9d05-511b956ebd05', tenant_id: 'ioet', deleted: null, name: 'abc'}],
isLoading: false,
message: 'Data fetch successfully!',
activityIdToEdit: '',
@@ -61,7 +63,7 @@ describe('DetailsFieldsComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [DetailsFieldsComponent, TechnologiesComponent],
- providers: [provideMockStore({ initialState: state })],
+ providers: [provideMockStore({initialState: state})],
imports: [FormsModule, ReactiveFormsModule],
}).compileComponents();
store = TestBed.inject(MockStore);
@@ -74,28 +76,17 @@ describe('DetailsFieldsComponent', () => {
beforeEach(() => {
fixture = TestBed.createComponent(DetailsFieldsComponent);
component = fixture.componentInstance;
- });
-
- it('should create', () => {
- expect(component).toBeTruthy();
- });
-
- it('should emit ngOnChange without data', () => {
- component.entryToEdit = null;
- component.ngOnChanges();
- expect(component.entryForm.value).toEqual(initialData);
- });
-
- it('should emit ngOnChange with new data', () => {
- const entryToEdit = {
+ entryToEdit = {
project_id: '',
activity_id: '',
uri: 'ticketUri',
start_date: null,
end_date: null,
description: '',
+ technologies: [],
+ id: 'xyz'
};
- const formValue = {
+ formValues = {
project_id: '',
activity_id: '',
uri: 'ticketUri',
@@ -105,9 +96,24 @@ describe('DetailsFieldsComponent', () => {
description: '',
technology: '',
};
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+
+ it('should emit ngOnChange without data', () => {
+ component.entryToEdit = null;
+ component.ngOnChanges();
+ expect(component.entryForm.value).toEqual(initialData);
+ });
+
+ it('should emit ngOnChange with new data', () => {
component.entryToEdit = entryToEdit;
+
component.ngOnChanges();
- expect(component.entryForm.value).toEqual(formValue);
+
+ expect(component.entryForm.value).toEqual(formValues);
});
it('should emit ngOnChange with new data', () => {
@@ -170,4 +176,79 @@ describe('DetailsFieldsComponent', () => {
};
expect(component.saveEntry.emit).toHaveBeenCalledWith(data);
});
+
+ it('when the current entry is not running, then the end hour input should be rendered', () => {
+ component.isEntryRunning = false;
+ fixture.detectChanges();
+
+ const endHourInput = fixture.debugElement.nativeElement.querySelector('#end_hour');
+ expect(endHourInput).toBeDefined();
+ });
+
+ it('when the current entry is running, then the end hour input should not be rendered', () => {
+ component.isEntryRunning = true;
+ fixture.detectChanges();
+
+ const endHourInput = fixture.debugElement.nativeElement.querySelector('#end_hour');
+ expect(endHourInput).toBeNull();
+ });
+
+ it('when creating a new entry, then the new entry should be marked as not running', () => {
+ component.entryToEdit = null;
+
+ expect(component.isEntryRunning).toBeFalse();
+ });
+
+ it('when editing entry that is currently running, then the entry should be marked as running', () => {
+ component.entryToEdit = {...entryToEdit, running: true};
+
+ fixture.componentInstance.ngOnChanges();
+
+ expect(component.isEntryRunning).toBeTrue();
+ });
+
+ it('when editing entry that already finished, then the entry should not be marked as running', () => {
+ component.entryToEdit = {...entryToEdit, running: false};
+
+ fixture.componentInstance.ngOnChanges();
+
+ expect(component.isEntryRunning).toBeFalse();
+ });
+
+ it('when editing entry that already finished, then the entry should not be marked as running', () => {
+ component.entryToEdit = {...entryToEdit, running: false};
+
+ fixture.componentInstance.ngOnChanges();
+
+ expect(component.isEntryRunning).toBeFalse();
+ });
+
+ it('when submitting a entry that is currently running, the end date should not be sent ', () => {
+ component.isEntryRunning = true;
+ spyOn(component.saveEntry, 'emit');
+
+ component.entryForm.setValue({...formValues, entry_date: '2020-06-11'});
+ component.onSubmit();
+ const data = {
+ project_id: '',
+ activity_id: '',
+ technologies: [],
+ description: '',
+ start_date: '2020-06-11T00:00',
+ uri: 'ticketUri',
+ };
+ expect(component.saveEntry.emit).toHaveBeenCalledWith(data);
+ });
+
+ it('when disabling current entry is running, then the end hour should be set to the current time', () => {
+ const datePipe: DatePipe = new DatePipe('en');
+ const currentTime = datePipe.transform(new Date(), 'HH:mm');
+
+ const checkIsEntryRunning: Element = fixture.debugElement.nativeElement.querySelector('#isEntryRunning');
+ checkIsEntryRunning.dispatchEvent(new Event('change'));
+ fixture.detectChanges();
+
+ const endHourInput: HTMLInputElement = fixture.debugElement.nativeElement.querySelector('#end_hour');
+ expect(endHourInput.value).toEqual(currentTime);
+ });
});
diff --git a/src/app/modules/shared/components/details-fields/details-fields.component.ts b/src/app/modules/shared/components/details-fields/details-fields.component.ts
index a141a9de0..623b0611c 100644
--- a/src/app/modules/shared/components/details-fields/details-fields.component.ts
+++ b/src/app/modules/shared/components/details-fields/details-fields.component.ts
@@ -1,26 +1,18 @@
-import {
- Component,
- OnChanges,
- OnInit,
- Input,
- Output,
- EventEmitter,
- ViewChild,
- ElementRef,
-} from '@angular/core';
+import { Component, ElementRef, EventEmitter, Input, OnChanges, OnInit, Output, ViewChild, } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';
-import { Store, select } from '@ngrx/store';
+import { select, Store } from '@ngrx/store';
import { formatDate } from '@angular/common';
-import { Project, Activity } from '../../models';
+import { Activity, Entry, Project } from '../../models';
import { ProjectState } from '../../../customer-management/components/projects/components/store/project.reducer';
import { TechnologyState } from '../../store/technology.reducers';
-import { LoadActivities, ActivityState, allActivities } from '../../../activities-management/store';
+import { ActivityState, allActivities, LoadActivities } from '../../../activities-management/store';
import { getProjects } from '../../../customer-management/components/projects/components/store/project.selectors';
import * as projectActions from '../../../customer-management/components/projects/components/store/project.actions';
import { EntryState } from '../../../time-clock/store/entry.reducer';
import * as entryActions from '../../../time-clock/store/entry.actions';
-import { getUpdateError, getCreateError } from 'src/app/modules/time-clock/store/entry.selectors';
+import { getCreateError, getUpdateError } from 'src/app/modules/time-clock/store/entry.selectors';
+
type Merged = TechnologyState & ProjectState & ActivityState & EntryState;
@Component({
@@ -29,7 +21,7 @@ type Merged = TechnologyState & ProjectState & ActivityState & EntryState;
styleUrls: ['./details-fields.component.scss'],
})
export class DetailsFieldsComponent implements OnChanges, OnInit {
- @Input() entryToEdit;
+ @Input() entryToEdit: Entry;
@Input() formType: string;
@Output() saveEntry = new EventEmitter();
@ViewChild('closeModal') closeModal: ElementRef;
@@ -38,8 +30,7 @@ export class DetailsFieldsComponent implements OnChanges, OnInit {
isLoading = false;
listProjects: Project[] = [];
activities: Activity[] = [];
- keyword = 'name';
- showlist: boolean;
+ isEntryRunning = false;
constructor(private formBuilder: FormBuilder, private store: Store
) {
this.entryForm = this.formBuilder.group({
@@ -84,6 +75,7 @@ export class DetailsFieldsComponent implements OnChanges, OnInit {
}
ngOnChanges(): void {
+ this.isEntryRunning = this.entryToEdit ? this.entryToEdit.running : false;
if (this.entryToEdit) {
this.selectedTechnologies = this.entryToEdit.technologies;
this.entryForm.setValue({
@@ -156,6 +148,16 @@ export class DetailsFieldsComponent implements OnChanges, OnInit {
end_date: `${entryDate}T${this.entryForm.value.end_hour.trim()}`,
uri: this.entryForm.value.uri,
};
+ if (this.isEntryRunning) {
+ delete entry.end_date;
+ }
this.saveEntry.emit(entry);
}
+
+ onIsRunningChange(event: any) {
+ this.isEntryRunning = event.currentTarget.checked;
+ if (!this.isEntryRunning) {
+ this.entryForm.patchValue({end_hour: formatDate(new Date(), 'HH:mm', 'en')});
+ }
+ }
}
diff --git a/src/app/modules/shared/models/entry.model.ts b/src/app/modules/shared/models/entry.model.ts
index ce821a200..31ea9c987 100644
--- a/src/app/modules/shared/models/entry.model.ts
+++ b/src/app/modules/shared/models/entry.model.ts
@@ -1,4 +1,5 @@
export interface Entry {
+ running?: boolean;
id: string;
start_date: Date;
end_date: Date;
@@ -8,6 +9,7 @@ export interface Entry {
uri?: string;
project_id?: string;
owner_email?: string;
+ description?: string;
}
export interface NewEntry {
diff --git a/src/app/modules/time-clock/pipes/time-details.pipe.spec.ts b/src/app/modules/time-clock/pipes/time-details.pipe.spec.ts
index f31691631..6cfd93ef4 100644
--- a/src/app/modules/time-clock/pipes/time-details.pipe.spec.ts
+++ b/src/app/modules/time-clock/pipes/time-details.pipe.spec.ts
@@ -1,4 +1,3 @@
-import { TimeDetails } from './../models/time.entry.summary';
import { TimeDetailsPipe } from './time-details.pipe';
describe('TimeDetailsPipe', () => {
diff --git a/src/app/modules/time-clock/store/entry.effects.ts b/src/app/modules/time-clock/store/entry.effects.ts
index 4423d3d2a..aae5ae452 100644
--- a/src/app/modules/time-clock/store/entry.effects.ts
+++ b/src/app/modules/time-clock/store/entry.effects.ts
@@ -1,8 +1,8 @@
-import { INFO_SAVED_SUCCESSFULLY, INFO_DELETE_SUCCESSFULLY } from './../../shared/messages';
+import { INFO_DELETE_SUCCESSFULLY, INFO_SAVED_SUCCESSFULLY } from './../../shared/messages';
import { Injectable } from '@angular/core';
-import { ofType, Actions, Effect } from '@ngrx/effects';
+import { Actions, Effect, ofType } from '@ngrx/effects';
import { Action } from '@ngrx/store';
-import { of, Observable } from 'rxjs';
+import { Observable, of } from 'rxjs';
import { ToastrService } from 'ngx-toastr';
import { catchError, map, mergeMap } from 'rxjs/operators';
import { EntryService } from '../services/entry.service';
@@ -10,7 +10,8 @@ import * as actions from './entry.actions';
@Injectable()
export class EntryEffects {
- constructor(private actions$: Actions, private entryService: EntryService, private toastrService: ToastrService) { }
+ constructor(private actions$: Actions, private entryService: EntryService, private toastrService: ToastrService) {
+ }
@Effect()
loadEntriesSummary$: Observable = this.actions$.pipe(
@@ -45,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.UpdateActiveEntry({id: activeEntry.id, end_date: endDate.toISOString()});
}
}
}),
@@ -118,9 +119,7 @@ export class EntryEffects {
mergeMap((entry) =>
this.entryService.updateActiveEntry(entry).pipe(
map((entryResponse) => {
- if (entryResponse.end_date && entry.start_date === null) {
- this.toastrService.success(INFO_SAVED_SUCCESSFULLY);
- }
+ this.toastrService.success(INFO_SAVED_SUCCESSFULLY);
return new actions.UpdateActiveEntrySuccess(entryResponse);
}),
catchError((error) => {
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 3513e46cd..62c09e3d8 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
@@ -249,4 +249,17 @@ describe('TimeEntriesComponent', () => {
expect(component.doSave).toHaveBeenCalledWith(entryToSave);
}));
+
+ it('when saving time entries, the time entries should be queried', () => {
+ const currentMonth = new Date().getMonth() + 1;
+ const entryToSave = {
+ project_id: 'project-id'
+ };
+ component.activeTimeEntry = null;
+ spyOn(store, 'dispatch');
+
+ component.saveEntry(entryToSave);
+
+ expect(store.dispatch).toHaveBeenCalledWith(new entryActions.LoadEntries(currentMonth));
+ });
});
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 84012aa36..2628d24e8 100644
--- a/src/app/modules/time-entries/pages/time-entries.component.ts
+++ b/src/app/modules/time-entries/pages/time-entries.component.ts
@@ -65,6 +65,7 @@ export class TimeEntriesComponent implements OnInit {
} else {
this.store.dispatch(new entryActions.CreateEntry(entry));
}
+ this.store.dispatch(new entryActions.LoadEntries(new Date().getMonth() + 1));
}
removeEntry(entryId: string) {