diff --git a/src/app/modules/customer-management/components/projects/components/create-project/create-project.component.spec.ts b/src/app/modules/customer-management/components/projects/components/create-project/create-project.component.spec.ts index 4d37e53a2..36ad588ae 100644 --- a/src/app/modules/customer-management/components/projects/components/create-project/create-project.component.spec.ts +++ b/src/app/modules/customer-management/components/projects/components/create-project/create-project.component.spec.ts @@ -17,6 +17,7 @@ describe('InputProjectComponent', () => { const state = { projects: [{ id: '', name: '', project_type_id: '' }], customerProjects: [{ id: '', name: '', project_type_id: '' }], + recentProjects: [], isLoading: false, message: '', projectToEdit: undefined, diff --git a/src/app/modules/customer-management/components/projects/components/project-list/project-list.component.spec.ts b/src/app/modules/customer-management/components/projects/components/project-list/project-list.component.spec.ts index e9ecc59d2..5632ea8bb 100644 --- a/src/app/modules/customer-management/components/projects/components/project-list/project-list.component.spec.ts +++ b/src/app/modules/customer-management/components/projects/components/project-list/project-list.component.spec.ts @@ -23,6 +23,7 @@ describe('ProjectListComponent', () => { const state: ProjectState = { projects: [project], customerProjects: [project], + recentProjects: [], isLoading: false, message: '', projectToEdit: undefined, diff --git a/src/app/modules/customer-management/components/projects/components/services/project.service.spec.ts b/src/app/modules/customer-management/components/projects/components/services/project.service.spec.ts index 02a1f169c..182633e3c 100644 --- a/src/app/modules/customer-management/components/projects/components/services/project.service.spec.ts +++ b/src/app/modules/customer-management/components/projects/components/services/project.service.spec.ts @@ -59,6 +59,16 @@ describe('ProjectService', () => { getProjectsRequest.flush(projectsList); }); + it('recent projects are read using GET from url/recent', () => { + const projectsFoundSize = projectsList.length; + service.getRecentProjects().subscribe((projectsInResponse) => { + expect(projectsInResponse.length).toBe(projectsFoundSize); + }); + const getProjectsRequest = httpMock.expectOne(`${service.url}/recent`); + expect(getProjectsRequest.request.method).toBe('GET'); + getProjectsRequest.flush(projectsList); + }); + it('create project using POST from url', () => { const project: Project[] = [{ id: '1', name: 'ccc', description: 'xxx', project_type_id: '123' }]; service.url = 'projects'; diff --git a/src/app/modules/customer-management/components/projects/components/services/project.service.ts b/src/app/modules/customer-management/components/projects/components/services/project.service.ts index bf7e8e37a..77ca1a82f 100644 --- a/src/app/modules/customer-management/components/projects/components/services/project.service.ts +++ b/src/app/modules/customer-management/components/projects/components/services/project.service.ts @@ -22,6 +22,10 @@ export class ProjectService { return this.http.get(this.url); } + getRecentProjects(): Observable { + return this.http.get(`${this.url}/recent`); + } + createProject(projectData): Observable { return this.http.post(this.url, projectData); } diff --git a/src/app/modules/customer-management/components/projects/components/store/project.actions.spec.ts b/src/app/modules/customer-management/components/projects/components/store/project.actions.spec.ts index d9943622a..736712800 100644 --- a/src/app/modules/customer-management/components/projects/components/store/project.actions.spec.ts +++ b/src/app/modules/customer-management/components/projects/components/store/project.actions.spec.ts @@ -27,6 +27,16 @@ describe('Actions for Projects', () => { expect(LoadCustomerProjectsFail.type).toEqual(actions.ProjectActionTypes.LOAD_CUSTOMER_PROJECTS_FAIL); }); + it('LoadRecentProjectsSuccess type is ProjectActionTypes.LOAD_RECENT_PROJECTS_SUCCESS', () => { + const action = new actions.LoadRecentProjectsSuccess([]); + expect(action.type).toEqual(actions.ProjectActionTypes.LOAD_RECENT_PROJECTS_SUCCESS); + }); + + it('LoadRecentProjectsFail type is ProjectActionTypes.LOAD_RECENT_PROJECTS_FAIL', () => { + const action = new actions.LoadRecentProjectsFail('error'); + expect(action.type).toEqual(actions.ProjectActionTypes.LOAD_RECENT_PROJECTS_FAIL); + }); + it('CreateProjectSuccess type is ProjectActionTypes.CREATE_PROJECT_SUCCESS', () => { const createProjectSuccess = new actions.CreateProjectSuccess({ id: '1', diff --git a/src/app/modules/customer-management/components/projects/components/store/project.actions.ts b/src/app/modules/customer-management/components/projects/components/store/project.actions.ts index 660274132..d0c26cc24 100644 --- a/src/app/modules/customer-management/components/projects/components/store/project.actions.ts +++ b/src/app/modules/customer-management/components/projects/components/store/project.actions.ts @@ -8,6 +8,8 @@ export enum ProjectActionTypes { LOAD_CUSTOMER_PROJECTS = '[Projects] LOAD_CUSTOMER_PROJECTS', LOAD_CUSTOMER_PROJECTS_SUCCESS = '[Projects] LOAD_CUSTOMER_PROJECTS_SUCCESS', LOAD_CUSTOMER_PROJECTS_FAIL = '[Projects] LOAD_CUSTOMER_PROJECTS_FAIL', + LOAD_RECENT_PROJECTS_SUCCESS = '[Projects] LOAD_RECENT_PROJECTS_SUCCESS', + LOAD_RECENT_PROJECTS_FAIL = '[Projects] LOAD_RECENT_PROJECTS_FAIL', CREATE_PROJECT = '[Projects] CREATE_PROJECT', CREATE_PROJECT_SUCCESS = '[Projects] CREATE_PROJECT_SUCCESS', CREATE_PROJECT_FAIL = '[Projects] CREATE_PROJECT_FAIL', @@ -60,6 +62,16 @@ export class LoadCustomerProjectsFail implements Action { constructor(public error: string) {} } +export class LoadRecentProjectsSuccess implements Action { + readonly type = ProjectActionTypes.LOAD_RECENT_PROJECTS_SUCCESS; + constructor(readonly payload: Project[]) {} +} + +export class LoadRecentProjectsFail implements Action { + public readonly type = ProjectActionTypes.LOAD_RECENT_PROJECTS_FAIL; + constructor(public error: string) {} +} + export class CreateProject implements Action { public readonly type = ProjectActionTypes.CREATE_PROJECT; @@ -150,6 +162,8 @@ export type ProjectActions = | LoadCustomerProjects | LoadCustomerProjectsSuccess | LoadCustomerProjectsFail + | LoadRecentProjectsSuccess + | LoadRecentProjectsFail | CreateProject | CreateProjectSuccess | CreateProjectFail diff --git a/src/app/modules/customer-management/components/projects/components/store/project.effects.spec.ts b/src/app/modules/customer-management/components/projects/components/store/project.effects.spec.ts index 28f7e3e79..3f0950542 100644 --- a/src/app/modules/customer-management/components/projects/components/store/project.effects.spec.ts +++ b/src/app/modules/customer-management/components/projects/components/store/project.effects.spec.ts @@ -55,6 +55,28 @@ describe('ProjectEffects', () => { }); }); + it('action type is LOAD_RECENT_PROJECTS_SUCCESS when service is executed sucessfully', async () => { + actions$ = of({ type: ProjectActionTypes.LOAD_PROJECTS }); + const serviceSpy = spyOn(service, 'getRecentProjects'); + serviceSpy.and.returnValue(of(projects)); + + effects.loadRecentProjects$.subscribe((action) => { + expect(action.type).toEqual(ProjectActionTypes.LOAD_RECENT_PROJECTS_SUCCESS); + }); + }); + + it('action type is LOAD_RECENT_PROJECTS_FAIL when service fail in execution', async () => { + actions$ = of({ type: ProjectActionTypes.LOAD_PROJECTS }); + const serviceSpy = spyOn(service, 'getRecentProjects'); + serviceSpy.and.returnValue(throwError({ error: { message: 'fail!' } })); + spyOn(toastrService, 'error'); + + effects.loadRecentProjects$.subscribe((action) => { + expect(toastrService.error).toHaveBeenCalled(); + expect(action.type).toEqual(ProjectActionTypes.LOAD_RECENT_PROJECTS_FAIL); + }); + }); + it('action type is UPDATE_PROJECT_SUCCESS when service is executed sucessfully', async () => { actions$ = of({ type: ProjectActionTypes.UPDATE_PROJECT, project }); spyOn(toastrService, 'success'); diff --git a/src/app/modules/customer-management/components/projects/components/store/project.effects.ts b/src/app/modules/customer-management/components/projects/components/store/project.effects.ts index 56aa7ed71..8382ff85b 100644 --- a/src/app/modules/customer-management/components/projects/components/store/project.effects.ts +++ b/src/app/modules/customer-management/components/projects/components/store/project.effects.ts @@ -49,6 +49,22 @@ export class ProjectEffects { ) ); + @Effect() + loadRecentProjects$: Observable = this.actions$.pipe( + ofType(actions.ProjectActionTypes.LOAD_PROJECTS), + mergeMap(() => + this.projectService.getRecentProjects().pipe( + map((projects) => { + return new actions.LoadRecentProjectsSuccess(projects); + }), + catchError((error) => { + this.toastrService.error(error.error.message); + return of(new actions.LoadRecentProjectsFail(error)); + }) + ) + ) + ); + @Effect() createProject$: Observable = this.actions$.pipe( ofType(actions.ProjectActionTypes.CREATE_PROJECT), diff --git a/src/app/modules/customer-management/components/projects/components/store/project.reducer.spec.ts b/src/app/modules/customer-management/components/projects/components/store/project.reducer.spec.ts index 5ce177ff9..cf387017a 100644 --- a/src/app/modules/customer-management/components/projects/components/store/project.reducer.spec.ts +++ b/src/app/modules/customer-management/components/projects/components/store/project.reducer.spec.ts @@ -5,7 +5,9 @@ import { projectReducer, ProjectState } from './project.reducer'; describe('projectReducer', () => { const initialState: ProjectState = { projects: [{ id: 'id', name: 'name', project_type_id: '', status: 'inactive' }], - customerProjects: [], isLoading: false, message: '', projectToEdit: undefined + customerProjects: [], + recentProjects: [], + isLoading: false, message: '', projectToEdit: undefined }; const archivedProject: Project = { id: '1', name: 'aaa', description: 'bbb', project_type_id: '123', status: 'inactive' }; const project: Project = { id: '1', name: 'aaa', description: 'bbb', project_type_id: '123', status: 'active' }; @@ -38,6 +40,23 @@ describe('projectReducer', () => { expect(state.customerProjects).toEqual([]); }); + it('on LoadRecentProjectsSuccess, projectsFound are saved in the store', () => { + const projectsFound: Project[] = [{ id: '1', name: 'abc', description: 'xxx', status: 'active' }]; + const newState = initialState; + newState.recentProjects = projectsFound; + const action = new actions.LoadRecentProjectsSuccess(projectsFound); + const state = projectReducer(initialState, action); + expect(state).toEqual(newState); + }); + + it('on LoadRecentProjectsFail, recentProjects equal []', () => { + const newState = initialState; + newState.recentProjects = []; + const action = new actions.LoadRecentProjectsFail('error'); + const state = projectReducer(initialState, action); + expect(state).toEqual(newState); + }); + it('on CreateProject, isLoading is true', () => { const action = new actions.CreateProject(project); const state = projectReducer(initialState, action); @@ -74,6 +93,7 @@ describe('projectReducer', () => { const currentState: ProjectState = { projects: [project], customerProjects: [project], + recentProjects: [project], isLoading: false, message: '', projectToEdit: project, @@ -124,6 +144,7 @@ describe('projectReducer', () => { const currentState: ProjectState = { projects: [project], customerProjects: [project], + recentProjects: [project], isLoading: false, message: '', projectToEdit: undefined, @@ -158,6 +179,7 @@ describe('projectReducer', () => { const currentState: ProjectState = { projects: [project], customerProjects: [archivedProject], + recentProjects: [project], isLoading: false, message: '', projectToEdit: project, diff --git a/src/app/modules/customer-management/components/projects/components/store/project.reducer.ts b/src/app/modules/customer-management/components/projects/components/store/project.reducer.ts index 86d2c51a4..fe16d8065 100644 --- a/src/app/modules/customer-management/components/projects/components/store/project.reducer.ts +++ b/src/app/modules/customer-management/components/projects/components/store/project.reducer.ts @@ -4,6 +4,7 @@ import { Project } from '../../../../../shared/models'; export interface ProjectState { projects: Project[]; customerProjects: Project[]; + recentProjects: Project[]; isLoading: boolean; message: string; projectToEdit: Project; @@ -12,6 +13,7 @@ export interface ProjectState { export const initialState = { projects: [], customerProjects: [], + recentProjects: [], isLoading: false, message: '', projectToEdit: undefined, @@ -65,6 +67,21 @@ export const projectReducer = (state: ProjectState = initialState, action: Proje }; } + case ProjectActionTypes.LOAD_RECENT_PROJECTS_SUCCESS: + return { + ...state, + recentProjects: action.payload, + isLoading: false + }; + + case ProjectActionTypes.LOAD_RECENT_PROJECTS_FAIL: { + return { + ...state, + recentProjects: [], + isLoading: false, + }; + } + case ProjectActionTypes.CREATE_PROJECT: { return { ...state, diff --git a/src/app/modules/customer-management/components/projects/components/store/project.selectors.spec.ts b/src/app/modules/customer-management/components/projects/components/store/project.selectors.spec.ts index 81b2e30d3..4fb0a587e 100644 --- a/src/app/modules/customer-management/components/projects/components/store/project.selectors.spec.ts +++ b/src/app/modules/customer-management/components/projects/components/store/project.selectors.spec.ts @@ -5,6 +5,7 @@ describe('ProjectSelectors', () => { const projectState = { projects: [], customerProjects: [], + recentProjects: [], isLoading: true, message: '', projectToEdit: { id: 'id', name: 'abc', description: 'xxx' }, @@ -25,6 +26,16 @@ describe('ProjectSelectors', () => { expect(selectors.getProjects.projector(projectState)).toEqual(filteredProjects); }); + it('should select getRecentProjects', () => { + const projects = [ + { id: '1', name: 'abc', description: 'xxx', status: 'active' }, + { id: '2', name: 'abc', description: 'xxx', status: 'active' }, + ]; + const projectState = { recentProjects: projects }; + + expect(selectors.getRecentProjects.projector(projectState)).toBe(projects); + }); + it('should select getProjectsToEdit', () => { const project = { id: 'id', name: 'abc', description: 'xxx' }; const projectState = { projectToEdit: project }; diff --git a/src/app/modules/customer-management/components/projects/components/store/project.selectors.ts b/src/app/modules/customer-management/components/projects/components/store/project.selectors.ts index 634ea0da8..72117aabb 100644 --- a/src/app/modules/customer-management/components/projects/components/store/project.selectors.ts +++ b/src/app/modules/customer-management/components/projects/components/store/project.selectors.ts @@ -9,6 +9,8 @@ export const getProjects = createSelector(getProjectState, (state: ProjectState) state?.projects.filter((item) => item.status !== 'inactive') ); +export const getRecentProjects = createSelector(getProjectState, (state: ProjectState) => state?.recentProjects); + export const getProjectToEdit = createSelector(getProjectState, (state: ProjectState) => state?.projectToEdit); export const getIsLoading = createSelector(getProjectState, (state: ProjectState) => state?.isLoading); diff --git a/src/app/modules/shared/components/details-fields/details-fields.component.html b/src/app/modules/shared/components/details-fields/details-fields.component.html index 10a6b47e6..c0dcea89d 100644 --- a/src/app/modules/shared/components/details-fields/details-fields.component.html +++ b/src/app/modules/shared/components/details-fields/details-fields.component.html @@ -12,33 +12,26 @@
- - - - -
-
- - - + + + +
+
+ {{item.customer.name}} - + {{item.name}} +
-
- + + - -
-
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 0db557b09..5fab7949b 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 @@ -22,6 +22,7 @@ import { NgxMaterialTimepickerModule } from 'ngx-material-timepicker'; import { DATE_FORMAT } from 'src/environments/environment'; import { DATE_FORMAT_YEAR } from 'src/environments/environment'; +import { Project } from '../../models'; describe('DetailsFieldsComponent', () => { type Merged = TechnologyState & ProjectState & EntryState; @@ -44,6 +45,7 @@ describe('DetailsFieldsComponent', () => { projects: { projects: [{ id: 'id', name: 'name', project_type_id: '', customer: { name: 'Juan', description: 'sadsa' } }], customerProjects: [{ id: 'id', name: 'name', description: 'description', project_type_id: '123' }], + recentProjects: [{ id: 'id', name: 'name', customer: { name: 'Juan'} }], isLoading: false, message: '', projectToEdit: undefined, @@ -154,12 +156,22 @@ describe('DetailsFieldsComponent', () => { }); it('onClearedComponent project id and name are set to empty', () => { - component.onClearedComponent(null); + const search = {term: ''}; + component.onClearedComponent(search); expect(component.project_id.value).toBe(''); expect(component.project_name.value).toBe(''); }); + it('should change the listProjectsShowed to listProjects if search is not empty on onClearedComponent', () => { + const search = {term: 'Ioet Inc.'}; + const listProjects: Project[] = [{ id: '1', name: 'abc', status: 'active' }]; + component.listProjects = listProjects; + component.onClearedComponent(search); + + expect(component.listProjectsShowed).toBe(component.listProjects); + }); + it('onSelectedProject project id and name are set using event data', () => { spyOn(component.entryForm, 'patchValue'); 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 b6b8e6610..90ce16e68 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 @@ -14,7 +14,7 @@ import { } from '../../../activities-management/store'; import * as projectActions from '../../../customer-management/components/projects/components/store/project.actions'; import { ProjectState } from '../../../customer-management/components/projects/components/store/project.reducer'; -import { getProjects } from '../../../customer-management/components/projects/components/store/project.selectors'; +import { getProjects, getRecentProjects } from '../../../customer-management/components/projects/components/store/project.selectors'; import * as entryActions from '../../../time-clock/store/entry.actions'; import { EntryState } from '../../../time-clock/store/entry.reducer'; import { Activity, Entry, Project } from '../../models'; @@ -27,8 +27,6 @@ import { DATE_FORMAT, DATE_FORMAT_YEAR } from 'src/environments/environment'; import { TechnologiesComponent } from '../technologies/technologies.component'; import { MatDatepicker } from '@angular/material/datepicker'; import { Observable } from 'rxjs'; -import { updateProjectStorage } from '../../utils/project-storage.util'; -import { PROJECTS_KEY_FOR_LOCAL_STORAGE } from '../../../../../environments/environment'; type Merged = TechnologyState & ProjectState & ActivityState & EntryState; @Component({ @@ -37,7 +35,6 @@ type Merged = TechnologyState & ProjectState & ActivityState & EntryState; styleUrls: ['./details-fields.component.scss'], }) export class DetailsFieldsComponent implements OnChanges, OnInit { - keyword = 'search_field'; @Input() entryToEdit: Entry; @Input() canMarkEntryAsWIP: boolean; @Output() saveEntry = new EventEmitter(); @@ -48,11 +45,12 @@ export class DetailsFieldsComponent implements OnChanges, OnInit { selectedTechnologies: string[] = []; isLoading = false; listProjects: Project[] = []; + listRecentProjects: Project[] = []; + listProjectsShowed: Project[] = []; activities$: Observable; goingToWorkOnThis = false; shouldRestartEntry = false; isTechnologiesDisabled = true; - projectKeyForLocalStorage = PROJECTS_KEY_FOR_LOCAL_STORAGE; constructor( private formBuilder: FormBuilder, @@ -85,11 +83,10 @@ export class DetailsFieldsComponent implements OnChanges, OnInit { projectWithSearchField.search_field = `${project.customer.name} - ${project.name}`; this.listProjects.push(projectWithSearchField); }); - - updateProjectStorage(projects); } }); + this.getRecentProjects(); this.store.dispatch(new LoadActivities()); this.activities$ = this.selectActiveActivities(); @@ -121,21 +118,28 @@ export class DetailsFieldsComponent implements OnChanges, OnInit { }); } - onClearedComponent(event) { - this.isTechnologiesDisabled = true; - this.entryForm.patchValue({ - project_id: '', - project_name: '', - }); + onClearedComponent({term}) { + const isSearchEmpty = (term === ''); + if (isSearchEmpty) { + this.isTechnologiesDisabled = true; + this.entryForm.patchValue({ + project_id: '', + project_name: '', + }); + } + this.listProjectsShowed = isSearchEmpty ? this.listRecentProjects : this.listProjects; } onSelectedProject(item) { - this.isTechnologiesDisabled = false; - this.projectSelected.emit({ projectId: item.id }); - this.entryForm.patchValue({ - project_id: item.id, - project_name: item.search_field, - }); + if (item) { + this.isTechnologiesDisabled = false; + this.projectSelected.emit({ projectId: item.id }); + this.entryForm.patchValue({ + project_id: item.id, + project_name: item.search_field, + }); + this.listProjectsShowed = this.listRecentProjects; + } } onStartDateChange($event: string) { @@ -153,10 +157,28 @@ export class DetailsFieldsComponent implements OnChanges, OnInit { return '00:00'; } + getRecentProjects(): void { + this.store.dispatch(new projectActions.LoadProjects()); + const recentProjects$ = this.store.pipe(select(getRecentProjects)); + recentProjects$.subscribe((projects) => { + if (projects) { + this.listRecentProjects = []; + projects.forEach((project) => { + const projectWithSearchField = { ...project }; + projectWithSearchField.search_field = `${project.customer.name} - ${project.name}`; + this.listRecentProjects.push(projectWithSearchField); + }); + this.listProjectsShowed = this.listRecentProjects; + }else{ + this.listRecentProjects = this.listProjects; + } + }); + } + ngOnChanges(): void { this.goingToWorkOnThis = this.entryToEdit ? this.entryToEdit.running ?? true : false; this.shouldRestartEntry = false; - + this.getRecentProjects(); if (this.entryToEdit) { this.isTechnologiesDisabled = false; this.selectedTechnologies = this.entryToEdit.technologies; diff --git a/src/app/modules/shared/utils/project-storage.util.spec.ts b/src/app/modules/shared/utils/project-storage.util.spec.ts deleted file mode 100644 index da0d580dc..000000000 --- a/src/app/modules/shared/utils/project-storage.util.spec.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { updateProjectStorage, getProjectsOnStorage } from './project-storage.util'; -import { Project } from '../models/project.model'; -import { PROJECTS_KEY_FOR_LOCAL_STORAGE } from '../../../../environments/environment'; - -describe('Project Storage', () => { - - let storageProjects: Project[]; - let projectsIdentifier: string; - let serverProjects: Project[]; - let testProject: Project; - let localStorageGetItemMock; - - beforeEach(() => { - projectsIdentifier = PROJECTS_KEY_FOR_LOCAL_STORAGE; - - testProject = { - customer: { - name: 'ioet Inc. (was E&Y)', - }, - id: 'f3630e59-9408-497e-945b-848112bd5a44', - name: 'Time Tracker', - customer_id: '20c96c4d-5e26-4426-a704-8bdd98c83319', - status: 'active', - }; - - storageProjects = [ - testProject - ]; - - serverProjects = [ - testProject, - { - customer: { - name: 'No Matter Name', - }, - id: 'no-matter-id', - name: 'Warby Parker', - customer_id: 'no-matter-id', - status: 'active', - } - ]; - - localStorageGetItemMock = spyOn(localStorage, 'getItem').and.returnValue(JSON.stringify(storageProjects)); - spyOn(localStorage, 'setItem'); - - }); - - it('If exists projects in localStorage and the server returns the same project, should keep the same localStorage variables', () => { - updateProjectStorage(serverProjects); - - expect(localStorage.setItem).toHaveBeenCalledWith(projectsIdentifier, JSON.stringify(storageProjects)); - }); - - it('If exists projects in localStorage and the server does not return that project, should update the localStorage variable', () => { - serverProjects.shift(); - - updateProjectStorage(serverProjects); - - expect(localStorage.setItem).toHaveBeenCalledWith(projectsIdentifier, JSON.stringify([])); - }); - - it('If Server projects is empty, should not update the localStorage', () => { - serverProjects = []; - - updateProjectStorage(serverProjects); - - expect(localStorage.setItem).toHaveBeenCalledTimes(0); - }); - - it('If variables does not exists on localStorage, getProjectsOnStorage should return undefined', () => { - projectsIdentifier = 'no-matter-identifier'; - - localStorageGetItemMock.and.returnValue(undefined); - - const projects = getProjectsOnStorage(projectsIdentifier); - - expect(projects).toBeUndefined(); - }); - - it('If variables not exists on localStorage, getProjectsOnStorage should return an array of Projects', () => { - const storageProjectsString = JSON.stringify(storageProjects); - localStorageGetItemMock.and.returnValue(storageProjectsString); - - const projects = getProjectsOnStorage(projectsIdentifier); - - expect(projects).toEqual(JSON.parse(storageProjectsString)); - }); -}); - diff --git a/src/app/modules/shared/utils/project-storage.util.ts b/src/app/modules/shared/utils/project-storage.util.ts deleted file mode 100644 index b0ce15b8a..000000000 --- a/src/app/modules/shared/utils/project-storage.util.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { Project } from '../models/project.model'; -import { PROJECTS_KEY_FOR_LOCAL_STORAGE as projectsKey } from '../../../../environments/environment'; -import { isEmpty } from 'lodash'; - -export function updateProjectStorage(serverProjects: Project[]): void { - const storageProjects: Project[] = getProjectsOnStorage(projectsKey); - - const isServerProjectsEmpty = isEmpty(serverProjects); - const updatedStorageProjects: Project[] = []; - - if (!isServerProjectsEmpty && storageProjects) { - storageProjects.forEach((storageProject: Project) => { - const project = serverProjects.find((serverProject) => serverProject.id === storageProject.id); - - if (project) { - updatedStorageProjects.push(storageProject); - } - }); - - const projectsForLocalStorage = JSON.stringify(updatedStorageProjects); - localStorage.setItem(projectsKey, projectsForLocalStorage); - } -} - -export function getProjectsOnStorage(projectsIdentifier: string): Project[] { - const projectsInsideLocalStorage: string = localStorage.getItem(projectsIdentifier); - return projectsInsideLocalStorage && JSON.parse(projectsInsideLocalStorage); -} - - 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 index c9ca37d15..95c7af36f 100644 --- 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 @@ -40,6 +40,7 @@ describe('EntryFieldsComponent', () => { projects: { projects: [{ id: 'id', name: 'name', project_type_id: '' }], customerProjects: [{ id: 'id', name: 'name', description: 'description', project_type_id: '123' }], + recentProjects: [], isLoading: false, message: '', projectToEdit: undefined, 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 0f3a9d550..0666957fa 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 @@ -2,52 +2,43 @@
- - - - -
-
- - - + [items]="listProjectsShowed" + (search)="onSearch($event)" + (change)="onSelect($event)" + (close)="loadActiveTimeEntry()"> + + +
+
+ {{item.customer.name}} - + {{item.name}} +
+
+ +   + +
-
- -   - -
-
- - - -
-
- + +
diff --git a/src/app/modules/time-clock/components/project-list-hover/project-list-hover.component.spec.ts b/src/app/modules/time-clock/components/project-list-hover/project-list-hover.component.spec.ts index 7b3eddd27..47c3372a2 100644 --- a/src/app/modules/time-clock/components/project-list-hover/project-list-hover.component.spec.ts +++ b/src/app/modules/time-clock/components/project-list-hover/project-list-hover.component.spec.ts @@ -25,6 +25,7 @@ describe('ProjectListHoverComponent', () => { projects: { projects: [], customerProjects: [{ id: 'id', name: 'name', description: 'description', project_type_id: '123' }], + recentProjects: [], isLoading: false, message: '', projectToEdit: undefined, @@ -136,6 +137,34 @@ describe('ProjectListHoverComponent', () => { expect(component.projectsForm.setValue).toHaveBeenCalledWith({ project_id: 'customer - xyz' }); }); + it('should change projects showed to recent projects list when search input is empty on onSearch', () => { + const recentProjects = [{ id: '1', customer: { name: 'customer'}, name: 'xyz' }]; + const search = {term: '', items: []}; + component.listRecentProjects = recentProjects; + component.onSearch(search); + + expect(component.listProjectsShowed).toEqual(recentProjects); + }); + + it('should change projects showed to projects list when search input is not empty on onSearch', () => { + const listProjects = [{id: '1', customer: { name: 'customer'}, name: 'xyz' }]; + const search = {term: 'xyz', items: []}; + component.listProjects = listProjects; + component.onSearch(search); + + expect(component.listProjectsShowed).toEqual(listProjects); + }); + + it('should clock in when select a project on onSelect', () => { + const [id, customer, name] = ['1', 'customer', 'xyz']; + const projectSelected = { id, customer: { name: customer}, name }; + spyOn(component, 'clockIn'); + component.showClockIn = true; + component.onSelect(projectSelected); + + expect(component.clockIn).toHaveBeenCalledWith(id, customer, name); + }); + // TODO Fix this test since it is throwing this error // Expected spy dispatch to have been called with: // [CreateEntry({ payload: Object({ project_id: '1', start_date: '2020-07-27T22:30:26.743Z', timezone_offset: 300 }), 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 d9b2c5731..06efc20d5 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 @@ -11,6 +11,7 @@ import * as entryActions from '../../store/entry.actions'; import { getIsLoading, getProjects, + getRecentProjects, } from './../../../customer-management/components/projects/components/store/project.selectors'; import { EntryActionTypes } from './../../store/entry.actions'; import { getActiveTimeEntry } from './../../store/entry.selectors'; @@ -18,8 +19,6 @@ import { Activity, } from '../../../shared/models'; import { LoadActivities } from './../../../activities-management/store/activity-management.actions'; import { allActivities } from 'src/app/modules/activities-management/store/activity-management.selectors'; import { head } from 'lodash'; -import { updateProjectStorage } from '../../../shared/utils/project-storage.util'; -import { PROJECTS_KEY_FOR_LOCAL_STORAGE } from '../../../../../environments/environment'; @Component({ selector: 'app-project-list-hover', @@ -27,8 +26,9 @@ import { PROJECTS_KEY_FOR_LOCAL_STORAGE } from '../../../../../environments/envi styleUrls: ['./project-list-hover.component.scss'], }) export class ProjectListHoverComponent implements OnInit, OnDestroy { - keyword = 'search_field'; listProjects: Project[] = []; + listRecentProjects: Project[] = []; + listProjectsShowed: Project[] = []; activities: Activity[] = []; activeEntry; projectsForm: FormGroup; @@ -36,9 +36,9 @@ export class ProjectListHoverComponent implements OnInit, OnDestroy { updateEntrySubscription: Subscription; isLoading$: Observable; projectsSubscription: Subscription; + recentProjectsSubscription: Subscription; activeEntrySubscription: Subscription; loadActivitiesSubscription: Subscription; - projectKeyForLocalStorage = PROJECTS_KEY_FOR_LOCAL_STORAGE; constructor( private formBuilder: FormBuilder, @@ -60,8 +60,6 @@ export class ProjectListHoverComponent implements OnInit, OnDestroy { projectWithSearchField.search_field = `${project.customer.name} - ${project.name}`; this.listProjects.push(projectWithSearchField); }); - - updateProjectStorage(projects); this.loadActiveTimeEntry(); }); this.store.dispatch(new LoadActivities()); @@ -69,6 +67,18 @@ export class ProjectListHoverComponent implements OnInit, OnDestroy { activities$.subscribe((response) => { this.activities = response; }); + + this.store.dispatch(new actions.LoadProjects()); + const recentProjects$ = this.store.pipe(select(getRecentProjects)); + this.recentProjectsSubscription = recentProjects$.subscribe((projects) => { + if (projects) { + this.listRecentProjects = projects; + this.listProjectsShowed = this.listRecentProjects; + }else{ + this.listRecentProjects = this.listProjects; + } + }); + this.updateEntrySubscription = this.actionsSubject$ .pipe(filter((action: any) => action.type === EntryActionTypes.UPDATE_ENTRY_SUCCESS)) .subscribe((action) => { @@ -126,8 +136,21 @@ export class ProjectListHoverComponent implements OnInit, OnDestroy { } } + onSearch({term}){ + const isSearchEmpty = (term === ''); + this.listProjectsShowed = isSearchEmpty ? this.listRecentProjects : this.listProjects; + } + + onSelect(project){ + if (project && this.showClockIn) { + this.clockIn(project.id, project.customer.name, project.name); + this.listProjectsShowed = this.listRecentProjects; + } + } + ngOnDestroy(): void { this.projectsSubscription.unsubscribe(); + this.recentProjectsSubscription.unsubscribe(); this.activeEntrySubscription.unsubscribe(); this.updateEntrySubscription.unsubscribe(); } diff --git a/src/environments/environment.ts b/src/environments/environment.ts index d5b558e9d..bf9638d8d 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -34,13 +34,6 @@ export const ROLES = { }, }; -/* -This variable is used by the ng-autocomplete component used in these files: -- details-fielfs.component.ts -- project-list-hover.component.ts -The purpose is to store the latest projects in the Local Storage with the next key. -*/ -export const PROJECTS_KEY_FOR_LOCAL_STORAGE = 'projectsSelected'; /* * For easier debugging in development mode, you can import the following file * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.