diff --git a/package-lock.json b/package-lock.json
index 8eab38262..e0567aaab 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -18537,8 +18537,7 @@
},
"mkdirp": {
"version": "0.5.1",
- "resolved": false,
- "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
+ "resolved": "",
"dev": true,
"optional": true,
"requires": {
@@ -18713,8 +18712,7 @@
"dependencies": {
"minimist": {
"version": "1.2.0",
- "resolved": false,
- "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
+ "resolved": "",
"dev": true,
"optional": true
}
@@ -19528,8 +19526,7 @@
},
"mkdirp": {
"version": "0.5.1",
- "resolved": false,
- "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
+ "resolved": "",
"dev": true,
"optional": true,
"requires": {
@@ -19704,8 +19701,7 @@
"dependencies": {
"minimist": {
"version": "1.2.0",
- "resolved": false,
- "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
+ "resolved": "",
"dev": true,
"optional": true
}
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 89313ee17..b23f7a9d6 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -29,6 +29,7 @@ import { ActivityListComponent } from './modules/activities-management/component
import { CreateActivityComponent } from './modules/activities-management/components/create-activity/create-activity.component';
import { FilterProjectPipe } from './modules/shared/pipes/filter-project/filter-project.pipe';
import { SearchComponent } from './modules/shared/components/search/search.component';
+import { EntryFieldsComponent } from './modules/time-clock/components/entry-fields/entry-fields.component';
import { HomeComponent } from './modules/home/home.component';
import { LoginComponent } from './modules/login/login.component';
import { ActivityEffects } from './modules/activities-management/store/activity-management.effects';
@@ -86,6 +87,7 @@ import { InjectTokenInterceptor } from './modules/shared/interceptors/inject.tok
ProjectListComponent,
ProjectTypeListComponent,
CreateProjectTypeComponent,
+ EntryFieldsComponent,
],
imports: [
CommonModule,
diff --git a/src/app/modules/shared/models/entry.model.ts b/src/app/modules/shared/models/entry.model.ts
index 85785b380..bea6b229e 100644
--- a/src/app/modules/shared/models/entry.model.ts
+++ b/src/app/modules/shared/models/entry.model.ts
@@ -6,10 +6,13 @@ export interface Entry {
activity: string;
technologies: string[];
comments?: string;
- ticket?: string;
+ uri?: string;
}
export interface NewEntry {
project_id: string;
- start_date: string;
+ start_date?: string;
+ description?: string;
+ technologies?: string[];
+ uri?: string;
}
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
new file mode 100644
index 000000000..be279282a
--- /dev/null
+++ b/src/app/modules/time-clock/components/entry-fields/entry-fields.component.html
@@ -0,0 +1,58 @@
+
diff --git a/src/app/modules/time-clock/components/entry-fields/entry-fields.component.scss b/src/app/modules/time-clock/components/entry-fields/entry-fields.component.scss
new file mode 100644
index 000000000..c26089d15
--- /dev/null
+++ b/src/app/modules/time-clock/components/entry-fields/entry-fields.component.scss
@@ -0,0 +1,91 @@
+@import '../../../../../styles/colors.scss';
+
+@mixin tagTechnology() {
+ background-color: $modal-button-secondary;
+ color: #ffffff;
+
+ &:hover {
+ opacity: 0.8;
+ }
+}
+
+.span-width {
+ width: 6rem;
+ background-image: $background-pantone;
+ color: white;
+}
+
+.hidden {
+ display: none;
+}
+
+.save-button-style {
+ background-color: $modal-button-primary;
+ color: white;
+}
+
+.close-button-style {
+ background-color: $modal-button-secondary;
+ color: white;
+}
+
+.technology-content {
+ box-shadow: 0 3px 8px 0 rgba(0, 0, 0, 0.2), 0 0 0 1px rgba(0, 0, 0, 0.08);
+ margin: 0 0 2rem 6rem;
+ max-height: 7.5rem;
+ overflow-y: auto;
+
+ .technology-list {
+ cursor: pointer;
+ font-size: 0.8rem;
+ margin-bottom: 0.1rem;
+ padding: 0.2rem 0.5rem;
+
+ &:hover {
+ opacity: 0.7;
+ }
+ }
+
+ .active {
+ background-color: #efefef;
+ }
+}
+
+.tags-content {
+ margin: 2rem 0;
+
+ div {
+ @include tagTechnology();
+
+ border-radius: 0.2rem;
+ box-shadow: 2px 2px 5px 0px rgba(0, 0, 0, 0.75);
+ font-size: 0.8rem;
+ padding: 0.1rem 1rem 0.2rem 1.5rem;
+ position: relative;
+ margin: 0 0.5rem 0.5rem 0;
+
+ i {
+ cursor: pointer;
+ }
+ }
+}
+
+.ng-autocomplete {
+ width: 100%;
+}
+
+.autocomplete::ng-deep .autocomplete-container {
+ border: 1px solid #ced4da;
+ border-radius: 0 0.25rem 0.25rem 0;
+ box-shadow: none;
+ height: 2rem;
+
+ .input-container {
+ height: 100%;
+
+ input {
+ border-radius: 0.25rem;
+ height: 100%;
+ }
+ }
+}
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
new file mode 100644
index 000000000..77fc9b534
--- /dev/null
+++ b/src/app/modules/time-clock/components/entry-fields/entry-fields.component.spec.ts
@@ -0,0 +1,188 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { provideMockStore, MockStore } from '@ngrx/store/testing';
+import { FormsModule, ReactiveFormsModule } from '@angular/forms';
+
+import { TechnologyState } from '../../../shared/store/technology.reducers';
+import { allTechnologies } from '../../../shared/store/technology.selectors';
+import { EntryFieldsComponent } from './entry-fields.component';
+import { ProjectState } from '../../../customer-management/components/projects/components/store/project.reducer';
+import { allProjects } from '../../../customer-management/components/projects/components/store/project.selectors';
+import { allEntries } from '../../store/entry.selectors';
+import * as actions from '../../../shared/store/technology.actions';
+import * as entryActions from '../../store/entry.actions';
+
+describe('EntryFieldsComponent', () => {
+ type Merged = TechnologyState & ProjectState;
+ let component: EntryFieldsComponent;
+ let fixture: ComponentFixture;
+ let store: MockStore;
+ let mockTechnologySelector;
+ let mockProjectsSelector;
+ let mockEntrySelector;
+ let length;
+
+ const state = {
+ projects: {
+ projectList: [{ id: 'id', name: 'name', description: 'description', project_type_id: '123' }],
+ isLoading: false,
+ message: '',
+ projectToEdit: undefined,
+ },
+ technologies: {
+ technologyList: { items: [{ name: 'java' }] },
+ isLoading: false,
+ },
+ activities: {
+ data: [{ id: 'fc5fab41-a21e-4155-9d05-511b956ebd05', tenant_id: 'ioet', deleted: null, name: 'Training 2' }],
+ isLoading: false,
+ message: 'Data fetch successfully!',
+ activityIdToEdit: '',
+ },
+ entries: {
+ active: {
+ id: 'id-15',
+ project_id: 'project-id-15',
+ description: 'description for active entry',
+ technologies: ['java', 'typescript'],
+ uri: 'abc',
+ },
+ entryList: [],
+ isLoading: false,
+ message: '',
+ },
+ };
+
+ const entry = {
+ id: 'id-15',
+ project_id: 'project-id-15',
+ description: 'description for active entry',
+ uri: 'abc',
+ };
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [EntryFieldsComponent],
+ providers: [provideMockStore({ initialState: state })],
+ imports: [FormsModule, ReactiveFormsModule],
+ }).compileComponents();
+ store = TestBed.inject(MockStore);
+ mockTechnologySelector = store.overrideSelector(allTechnologies, state.technologies);
+ mockProjectsSelector = store.overrideSelector(allProjects, state.projects);
+ mockEntrySelector = store.overrideSelector(allEntries, state.entries);
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(EntryFieldsComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+
+ it('should set data in entryForm', () => {
+ const entryDataForm = {
+ description: 'description for active entry',
+ technologies: null,
+ uri: 'abc',
+ };
+
+ spyOn(component.entryForm, 'patchValue');
+
+ component.setDataToUpdate(entry);
+
+ expect(component.entryForm.patchValue).toHaveBeenCalledTimes(1);
+ expect(component.entryForm.patchValue).toHaveBeenCalledWith(
+ { description: entryDataForm.description, uri: entryDataForm.uri, });
+ expect(component.selectedTechnology).toEqual([]);
+ });
+
+ it('should dispatch FindTechnology action #getTechnologies', () => {
+ const value = 'java';
+ spyOn(store, 'dispatch');
+ length = value.length;
+ component.getTechnologies(value);
+
+ expect(component.showlist).toBe(true);
+ expect(store.dispatch).toHaveBeenCalledWith(new actions.FindTechnology(value));
+ });
+
+ it('should NOT dispatch FindTechnology action #getTechnologies', () => {
+ const value = 'j';
+ spyOn(store, 'dispatch');
+ length = value.length;
+ component.getTechnologies(value);
+
+ expect(store.dispatch).not.toHaveBeenCalledWith(new actions.FindTechnology(value));
+ });
+
+ it('should add a new tag #setTechnology', () => {
+ spyOn(store, 'dispatch');
+ const name = 'ngrx';
+ component.selectedTechnology = ['java', 'javascript'];
+ component.selectedTechnology.indexOf(name);
+ length = component.selectedTechnology.length;
+
+ const newEntry = {
+ id: 'id-15',
+ project_id: 'project-id-15',
+ uri: 'abc',
+ };
+
+ component.setTechnology(name);
+ expect(component.selectedTechnology.length).toBe(3);
+ expect(store.dispatch).toHaveBeenCalledWith(
+ new entryActions.UpdateActiveEntry({ ...newEntry, technologies: component.selectedTechnology })
+ );
+ });
+
+ it('should NOT add a new tag #setTechnology', () => {
+ const name = 'ngrx';
+ component.selectedTechnology = [
+ 'java',
+ 'javascript',
+ 'angular',
+ 'angular-ui',
+ 'typescript',
+ 'scss',
+ 'bootstrap',
+ 'jasmine',
+ 'karme',
+ 'github',
+ ];
+ component.selectedTechnology.indexOf(name);
+ length = component.selectedTechnology.length;
+ component.setTechnology(name);
+ expect(component.selectedTechnology.length).toBe(10);
+ });
+
+ it('should call the removeTag function #setTechnology', () => {
+ const name = 'java';
+ component.selectedTechnology = ['java', 'javascript'];
+ const index = component.selectedTechnology.indexOf(name);
+ spyOn(component, 'removeTag');
+ component.setTechnology(name);
+ expect(component.removeTag).toHaveBeenCalledWith(index);
+ });
+
+ it('should dispatch UpdateActiveEntry action #removeTag function', () => {
+ spyOn(store, 'dispatch');
+ const index = 1;
+ const newEntry = {
+ id: 'id-15',
+ project_id: 'project-id-15',
+ uri: 'abc',
+ };
+ component.selectedTechnology = ['java', 'angular'];
+ const technologies = component.selectedTechnology.filter((item) => item !== component.selectedTechnology[index]);
+ component.removeTag(index);
+ expect(store.dispatch).toHaveBeenCalledWith(new entryActions.UpdateActiveEntry({ ...newEntry, technologies }));
+ });
+
+ it('should dispatch UpdateActiveEntry action #onSubmit', () => {
+ spyOn(store, 'dispatch');
+ component.onSubmit();
+ expect(store.dispatch).toHaveBeenCalledWith(new entryActions.UpdateActiveEntry(entry));
+ });
+});
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
new file mode 100644
index 000000000..bdd9f4bf1
--- /dev/null
+++ b/src/app/modules/time-clock/components/entry-fields/entry-fields.component.ts
@@ -0,0 +1,114 @@
+import { Component, OnInit, ViewChild, ElementRef, Renderer2 } from '@angular/core';
+import { FormBuilder, FormGroup } from '@angular/forms';
+import { Store, select } from '@ngrx/store';
+
+import { Technology, Activity, NewEntry } from '../../../shared/models';
+import { allTechnologies } from '../../../shared/store/technology.selectors';
+import * as actions from '../../../shared/store/technology.actions';
+
+import { ProjectState } from '../../../customer-management/components/projects/components/store/project.reducer';
+import { TechnologyState } from '../../../shared/store/technology.reducers';
+import { LoadActivities, ActivityState, allActivities } from '../../../activities-management/store';
+
+import { allEntries } from '../../store/entry.selectors';
+import * as entryActions from '../../store/entry.actions';
+
+type Merged = TechnologyState & ProjectState & ActivityState;
+
+@Component({
+ selector: 'app-entry-fields',
+ templateUrl: './entry-fields.component.html',
+ styleUrls: ['./entry-fields.component.scss'],
+})
+export class EntryFieldsComponent implements OnInit {
+ @ViewChild('list') list: ElementRef;
+ entryForm: FormGroup;
+ technology: Technology;
+ selectedTechnology: string[] = [];
+ isLoading = false;
+ activities: Activity[] = [];
+ keyword = 'name';
+ showlist: boolean;
+ activeEntry;
+ newData;
+
+ constructor(private formBuilder: FormBuilder, private store: Store, private renderer: Renderer2) {
+ this.renderer.listen('window', 'click', (e: Event) => {
+ if (this.showlist && !this.list.nativeElement.contains(e.target)) {
+ this.showlist = false;
+ }
+ });
+ this.entryForm = this.formBuilder.group({
+ description: '',
+ uri: '',
+ });
+ }
+
+ ngOnInit(): void {
+ const technologies$ = this.store.pipe(select(allTechnologies));
+ technologies$.subscribe((response) => {
+ this.isLoading = response.isLoading;
+ this.technology = response.technologyList;
+ });
+
+ this.store.dispatch(new LoadActivities());
+ const activities$ = this.store.pipe(select(allActivities));
+ activities$.subscribe((response) => {
+ this.activities = response;
+ });
+
+ const activeEntry$ = this.store.pipe(select(allEntries));
+ activeEntry$.subscribe((response) => {
+ this.activeEntry = response.active;
+ this.setDataToUpdate(this.activeEntry);
+ this.newData = {
+ id: this.activeEntry.id,
+ project_id: this.activeEntry.project_id,
+ uri: this.activeEntry.uri,
+ };
+ });
+ }
+
+ setDataToUpdate(entryData: NewEntry) {
+ if (entryData) {
+ this.entryForm.patchValue({
+ description: entryData.description,
+ uri: entryData.uri,
+ });
+ if (entryData.technologies) {
+ this.selectedTechnology = entryData.technologies;
+ } else {
+ this.selectedTechnology = [];
+ }
+ }
+ }
+
+ getTechnologies(value) {
+ if (value.length >= 2) {
+ this.showlist = true;
+ this.store.dispatch(new actions.FindTechnology(value));
+ }
+ }
+
+ setTechnology(name: string) {
+ const index = this.selectedTechnology.indexOf(name);
+
+ if (index > -1) {
+ this.removeTag(index);
+ } else if (this.selectedTechnology.length < 10) {
+ this.selectedTechnology = [...this.selectedTechnology, name];
+ this.store.dispatch(
+ new entryActions.UpdateActiveEntry({ ...this.newData, technologies: this.selectedTechnology })
+ );
+ }
+ }
+
+ removeTag(index: number) {
+ const technologies = this.selectedTechnology.filter((item) => item !== this.selectedTechnology[index]);
+ this.store.dispatch(new entryActions.UpdateActiveEntry({ ...this.newData, technologies }));
+ }
+
+ onSubmit() {
+ this.store.dispatch(new entryActions.UpdateActiveEntry({ ...this.newData, ...this.entryForm.value }));
+ }
+}
diff --git a/src/app/modules/time-clock/components/project-list-hover/project-list-hover.component.html b/src/app/modules/time-clock/components/project-list-hover/project-list-hover.component.html
index f3b0e0da6..72cb6c151 100644
--- a/src/app/modules/time-clock/components/project-list-hover/project-list-hover.component.html
+++ b/src/app/modules/time-clock/components/project-list-hover/project-list-hover.component.html
@@ -1,13 +1,26 @@
+
+
+
+
{
let mockProjectsSelector;
const state = {
- projectList: [{ id: 'id', name: 'name', description: 'description', project_type_id: '123' }],
- isLoading: false,
- message: '',
- projectToEdit: undefined,
+ projects: {
+ projectList: [{ id: 'id', name: 'name', description: 'description', project_type_id: '123' }],
+ isLoading: false,
+ message: '',
+ projectToEdit: undefined,
+ },
+ entries: {
+ active: {
+ project_id: '2b87372b-3d0d-4dc0-832b-ae5863cd39e5',
+ start_date: '2020-04-23T16:11:06.455000+00:00',
+ technologies: ['java', 'typescript'],
+ },
+ entryList: [],
+ isLoading: false,
+ message: '',
+ },
};
beforeEach(async(() => {
@@ -29,7 +41,7 @@ describe('ProjectListHoverComponent', () => {
imports: [HttpClientTestingModule],
}).compileComponents();
store = TestBed.inject(MockStore);
- mockProjectsSelector = store.overrideSelector(allProjects, state);
+ mockProjectsSelector = store.overrideSelector(allProjects, state.projects);
}));
beforeEach(() => {
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 ced1dd7a9..bd70f87e4 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
@@ -6,6 +6,8 @@ import { ProjectState } from '../../../customer-management/components/projects/c
import * as actions from '../../../customer-management/components/projects/components/store/project.actions';
import * as entryActions from '../../store/entry.actions';
+import { selectActiveEntry } from '../../store/entry.selectors';
+
@Component({
selector: 'app-project-list-hover',
templateUrl: './project-list-hover.component.html',
@@ -20,6 +22,7 @@ export class ProjectListHoverComponent implements OnInit {
filterProjects = '';
showButton = '';
keyword = 'name';
+ nameActiveProject: string;
constructor(private store: Store
) {}
@@ -31,12 +34,21 @@ export class ProjectListHoverComponent implements OnInit {
this.isLoading = response.isLoading;
this.listProjects = response.projectList;
});
+
+ this.store.dispatch(new entryActions.LoadActiveEntry());
+ const activeEntry$ = this.store.pipe(select(selectActiveEntry));
+
+ activeEntry$.subscribe((response) => {
+ if (response) {
+ this.nameActiveProject = response.name;
+ this.showFields.emit(true);
+ }
+ });
}
clockIn(id: string) {
const newEntry = { project_id: id, start_date: new Date().toISOString() };
this.store.dispatch(new entryActions.CreateEntry(newEntry));
this.selectedId = id;
- this.showFields.emit(true);
}
}
diff --git a/src/app/modules/time-clock/pages/time-clock.component.html b/src/app/modules/time-clock/pages/time-clock.component.html
index 17df8f99e..4e8a461a0 100644
--- a/src/app/modules/time-clock/pages/time-clock.component.html
+++ b/src/app/modules/time-clock/pages/time-clock.component.html
@@ -35,9 +35,10 @@ 14:00
-
+
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 458c92926..50e9ae77e 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
@@ -9,7 +9,7 @@ import { ProjectState } from '../../customer-management/components/projects/comp
import { ProjectListHoverComponent } from '../components';
import { ProjectService } from '../../customer-management/components/projects/components/services/project.service';
import { FilterProjectPipe } from '../../shared/pipes';
-import {AzureAdB2CService} from '../../login/services/azure.ad.b2c.service';
+import { AzureAdB2CService } from '../../login/services/azure.ad.b2c.service';
describe('TimeClockComponent', () => {
let component: TimeClockComponent;
@@ -28,6 +28,16 @@ describe('TimeClockComponent', () => {
isLoading: false,
message: 'message',
},
+ entries: {
+ active: {
+ project_id: '2b87372b-3d0d-4dc0-832b-ae5863cd39e5',
+ start_date: '2020-04-23T16:11:06.455000+00:00',
+ technologies: ['java', 'typescript'],
+ },
+ entryList: [],
+ isLoading: false,
+ message: '',
+ },
};
beforeEach(async(() => {
diff --git a/src/app/modules/time-clock/services/entry.service.ts b/src/app/modules/time-clock/services/entry.service.ts
index ca3e2de9e..c4d2b7ea2 100644
--- a/src/app/modules/time-clock/services/entry.service.ts
+++ b/src/app/modules/time-clock/services/entry.service.ts
@@ -12,7 +12,16 @@ export class EntryService {
constructor(private http: HttpClient) {}
+ loadActiveEntry(): Observable
{
+ return this.http.get(`${this.baseUrl}/running`);
+ }
+
createEntry(entryData): Observable {
return this.http.post(this.baseUrl, entryData);
}
+
+ updateActiveEntry(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 eaf1d45b3..b2c3380a8 100644
--- a/src/app/modules/time-clock/store/entry.actions.spec.ts
+++ b/src/app/modules/time-clock/store/entry.actions.spec.ts
@@ -1,6 +1,16 @@
import * as actions from './entry.actions';
describe('Actions for Entries', () => {
+ it('LoadActiveEntrySuccess type is EntryActionTypes.LOAD_ACTIVE_ENTRY_SUCCESS', () => {
+ const loadActiveEntrySuccess = new actions.LoadActiveEntrySuccess([]);
+ expect(loadActiveEntrySuccess.type).toEqual(actions.EntryActionTypes.LOAD_ACTIVE_ENTRY_SUCCESS);
+ });
+
+ it('LoadActiveEntryFail type is EntryActionTypes.LOAD_ACTIVE_ENTRY_FAIL', () => {
+ const loadActiveEntryFail = new actions.LoadActiveEntryFail('error');
+ expect(loadActiveEntryFail.type).toEqual(actions.EntryActionTypes.LOAD_ACTIVE_ENTRY_FAIL);
+ });
+
it('CreateEntrySuccess type is EntryActionTypes.CREATE_ENTRY_SUCCESS', () => {
const createEntrySuccess = new actions.CreateEntrySuccess({
project_id: '1',
@@ -13,4 +23,17 @@ describe('Actions for Entries', () => {
const createEntryFail = new actions.CreateEntryFail('error');
expect(createEntryFail.type).toEqual(actions.EntryActionTypes.CREATE_ENTRY_FAIL);
});
+
+ it('UpdateActiveEntrySuccess type is EntryActionTypes.UDPATE_ACTIVE_ENTRY_SUCCESS', () => {
+ const updateActiveEntrySuccess = new actions.UpdateActiveEntrySuccess({
+ project_id: '1',
+ description: 'It is good for learning',
+ });
+ expect(updateActiveEntrySuccess.type).toEqual(actions.EntryActionTypes.UDPATE_ACTIVE_ENTRY_SUCCESS);
+ });
+
+ it('UpdateActiveEntryFail type is EntryActionTypes.UDPATE_ACTIVE_ENTRY_FAIL', () => {
+ const updateActiveEntryFail = new actions.UpdateActiveEntryFail('error');
+ expect(updateActiveEntryFail.type).toEqual(actions.EntryActionTypes.UDPATE_ACTIVE_ENTRY_FAIL);
+ });
});
diff --git a/src/app/modules/time-clock/store/entry.actions.ts b/src/app/modules/time-clock/store/entry.actions.ts
index 5d39c38da..34df7e70a 100644
--- a/src/app/modules/time-clock/store/entry.actions.ts
+++ b/src/app/modules/time-clock/store/entry.actions.ts
@@ -2,9 +2,30 @@ import { Action } from '@ngrx/store';
import { NewEntry } from '../../shared/models';
export enum EntryActionTypes {
+ LOAD_ACTIVE_ENTRY = '[Entry] LOAD_ACTIVE_ENTRY',
+ LOAD_ACTIVE_ENTRY_SUCCESS = '[Entry] LOAD_ACTIVE_ENTRY_SUCCESS',
+ LOAD_ACTIVE_ENTRY_FAIL = '[Entry] LOAD_ACTIVE_ENTRY_FAIL',
CREATE_ENTRY = '[Entry] CREATE_ENTRY',
CREATE_ENTRY_SUCCESS = '[Entry] CREATE_ENTRY_SUCCESS',
CREATE_ENTRY_FAIL = '[Entry] CREATE_ENTRY_FAIL',
+ UDPATE_ACTIVE_ENTRY = '[Entry] UDPATE_ACTIVE_ENTRY',
+ UDPATE_ACTIVE_ENTRY_SUCCESS = '[Entry] UDPATE_ACTIVE_ENTRY_SUCCESS',
+ UDPATE_ACTIVE_ENTRY_FAIL = '[Entry] UDPATE_ACTIVE_ENTRY_FAIL',
+}
+
+export class LoadActiveEntry implements Action {
+ public readonly type = EntryActionTypes.LOAD_ACTIVE_ENTRY;
+}
+
+export class LoadActiveEntrySuccess implements Action {
+ readonly type = EntryActionTypes.LOAD_ACTIVE_ENTRY_SUCCESS;
+ constructor(readonly payload: NewEntry[]) {}
+}
+
+export class LoadActiveEntryFail implements Action {
+ public readonly type = EntryActionTypes.LOAD_ACTIVE_ENTRY_FAIL;
+
+ constructor(public error: string) {}
}
export class CreateEntry implements Action {
@@ -25,4 +46,31 @@ export class CreateEntryFail implements Action {
constructor(public error: string) {}
}
-export type EntryActions = CreateEntry | CreateEntrySuccess | CreateEntryFail;
+export class UpdateActiveEntry implements Action {
+ public readonly type = EntryActionTypes.UDPATE_ACTIVE_ENTRY;
+
+ constructor(public payload: NewEntry) {}
+}
+
+export class UpdateActiveEntrySuccess implements Action {
+ public readonly type = EntryActionTypes.UDPATE_ACTIVE_ENTRY_SUCCESS;
+
+ constructor(public payload: NewEntry) {}
+}
+
+export class UpdateActiveEntryFail implements Action {
+ public readonly type = EntryActionTypes.UDPATE_ACTIVE_ENTRY_FAIL;
+
+ constructor(public error: string) {}
+}
+
+export type EntryActions =
+ | LoadActiveEntry
+ | LoadActiveEntrySuccess
+ | LoadActiveEntryFail
+ | CreateEntry
+ | CreateEntrySuccess
+ | CreateEntryFail
+ | UpdateActiveEntry
+ | UpdateActiveEntrySuccess
+ | UpdateActiveEntryFail;
diff --git a/src/app/modules/time-clock/store/entry.effects.ts b/src/app/modules/time-clock/store/entry.effects.ts
index aa6d305e2..c6763fb4c 100644
--- a/src/app/modules/time-clock/store/entry.effects.ts
+++ b/src/app/modules/time-clock/store/entry.effects.ts
@@ -10,6 +10,19 @@ import * as actions from './entry.actions';
export class EntryEffects {
constructor(private actions$: Actions, private entryService: EntryService) {}
+ @Effect()
+ loadActiveEntry$: Observable = this.actions$.pipe(
+ ofType(actions.EntryActionTypes.LOAD_ACTIVE_ENTRY),
+ mergeMap(() =>
+ this.entryService.loadActiveEntry().pipe(
+ map((activeEntry) => {
+ return new actions.LoadActiveEntrySuccess(activeEntry);
+ }),
+ catchError((error) => of(new actions.LoadActiveEntryFail(error)))
+ )
+ )
+ );
+
@Effect()
createEntry$: Observable = this.actions$.pipe(
ofType(actions.EntryActionTypes.CREATE_ENTRY),
@@ -23,4 +36,18 @@ export class EntryEffects {
)
)
);
+
+ @Effect()
+ updateActiveEntry$: Observable = this.actions$.pipe(
+ ofType(actions.EntryActionTypes.UDPATE_ACTIVE_ENTRY),
+ map((action: actions.UpdateActiveEntry) => action.payload),
+ mergeMap((project) =>
+ this.entryService.updateActiveEntry(project).pipe(
+ map((projectData) => {
+ return new actions.UpdateActiveEntrySuccess(projectData);
+ }),
+ catchError((error) => 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 c93a8b994..726b0b989 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,29 @@ import * as actions from './entry.actions';
import { entryReducer, EntryState } from './entry.reducer';
describe('entryReducer', () => {
- const initialState: EntryState = { entryList: [], isLoading: false, message: '' };
+ const initialState: EntryState = { active: null, entryList: [], isLoading: false, message: '' };
+ const newEntry: NewEntry = { project_id: '112', description: 'aaa', technologies: ['angular', 'typescript'] };
+
+ it('on LoadActiveEntry, isLoading is true', () => {
+ const action = new actions.LoadActiveEntry();
+ const state = entryReducer(initialState, action);
+ expect(state.isLoading).toEqual(true);
+ });
+
+ it('on LoadActiveEntrySuccess, activeEntryFound are saved in the store', () => {
+ const activeEntryFound: NewEntry[] = [
+ { project_id: '123', description: 'description', technologies: ['angular', 'javascript'] },
+ ];
+ const action = new actions.LoadActiveEntrySuccess(activeEntryFound);
+ const state = entryReducer(initialState, action);
+ expect(state.active).toEqual(activeEntryFound);
+ });
+
+ it('on LoadActiveEntryFail, active tobe null', () => {
+ const action = new actions.LoadActiveEntryFail('error');
+ const state = entryReducer(initialState, action);
+ expect(state.active).toBe(null);
+ });
it('on CreateEntry, isLoading is true', () => {
const entry: NewEntry = { project_id: '1', start_date: '2020-04-21T19:51:36.559000+00:00' };
@@ -29,4 +51,33 @@ describe('entryReducer', () => {
expect(state.entryList).toEqual([]);
expect(state.isLoading).toEqual(false);
});
+
+ it('on UpdateActiveEntry, isLoading is true', () => {
+ const action = new actions.UpdateActiveEntry(newEntry);
+ const state = entryReducer(initialState, action);
+
+ expect(state.isLoading).toEqual(true);
+ });
+
+ it('on UpdateActiveEntrySuccess, active is saved in the store', () => {
+ const currentState: EntryState = {
+ active: newEntry,
+ entryList: [],
+ isLoading: false,
+ message: '',
+ };
+ const action = new actions.UpdateActiveEntrySuccess(newEntry);
+ const state = entryReducer(currentState, action);
+
+ expect(state.active).toEqual(newEntry);
+ expect(state.isLoading).toEqual(false);
+ });
+
+ it('on UpdateActiveEntryFail, active to be null', () => {
+ const action = new actions.UpdateActiveEntryFail('error');
+ const state = entryReducer(initialState, action);
+
+ expect(state.active).toBe(null);
+ expect(state.isLoading).toEqual(false);
+ });
});
diff --git a/src/app/modules/time-clock/store/entry.reducer.ts b/src/app/modules/time-clock/store/entry.reducer.ts
index 0dd910a90..67e5b2aa6 100644
--- a/src/app/modules/time-clock/store/entry.reducer.ts
+++ b/src/app/modules/time-clock/store/entry.reducer.ts
@@ -1,13 +1,15 @@
import { EntryActions, EntryActionTypes } from './entry.actions';
-import { Entry } from '../../shared/models';
+import { Entry, NewEntry } from '../../shared/models';
export interface EntryState {
+ active: NewEntry;
entryList: Entry[];
isLoading: boolean;
message: string;
}
export const initialState = {
+ active: null,
entryList: [],
isLoading: false,
message: '',
@@ -15,6 +17,28 @@ export const initialState = {
export const entryReducer = (state: EntryState = initialState, action: EntryActions) => {
switch (action.type) {
+ case EntryActionTypes.LOAD_ACTIVE_ENTRY: {
+ return {
+ ...state,
+ isLoading: true,
+ };
+ }
+ case EntryActionTypes.LOAD_ACTIVE_ENTRY_SUCCESS:
+ return {
+ ...state,
+ active: action.payload,
+ isLoading: false,
+ };
+
+ case EntryActionTypes.LOAD_ACTIVE_ENTRY_FAIL: {
+ return {
+ ...state,
+ active: null,
+ isLoading: false,
+ message: 'Something went wrong fetching active entry!',
+ };
+ }
+
case EntryActionTypes.CREATE_ENTRY: {
return {
...state,
@@ -33,12 +57,38 @@ export const entryReducer = (state: EntryState = initialState, action: EntryActi
case EntryActionTypes.CREATE_ENTRY_FAIL: {
return {
+ ...state,
entryList: [],
isLoading: false,
message: action.error,
};
}
+ case EntryActionTypes.UDPATE_ACTIVE_ENTRY: {
+ return {
+ ...state,
+ isLoading: true,
+ };
+ }
+
+ case EntryActionTypes.UDPATE_ACTIVE_ENTRY_SUCCESS: {
+ const activeEntry = { ...state.active, ...action.payload };
+
+ return {
+ ...state,
+ active: activeEntry,
+ isLoading: false,
+ };
+ }
+
+ case EntryActionTypes.UDPATE_ACTIVE_ENTRY_FAIL: {
+ return {
+ ...state,
+ active: null,
+ isLoading: false,
+ };
+ }
+
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
new file mode 100644
index 000000000..3ac5beec5
--- /dev/null
+++ b/src/app/modules/time-clock/store/entry.selectors.ts
@@ -0,0 +1,18 @@
+import { createFeatureSelector, createSelector } from '@ngrx/store';
+
+import { EntryState } from './entry.reducer';
+
+const getEntryState = createFeatureSelector('entries');
+
+export const allEntries = createSelector(getEntryState, (state: EntryState) => {
+ return state;
+});
+
+export const selectProjects = (state) => state.projects.projectList;
+export const selectEntries = (state) => state.entries.active;
+
+export const selectActiveEntry = createSelector(selectProjects, selectEntries, (selectedProject, selectedEntry) => {
+ if (selectedProject && selectedEntry) {
+ return selectedProject.find((project) => project.id === selectedEntry.project_id);
+ }
+});