diff --git a/config/webpack/dev.js b/config/webpack/dev.js index a6514f8..0bd9544 100644 --- a/config/webpack/dev.js +++ b/config/webpack/dev.js @@ -28,6 +28,9 @@ module.exports = merge.strategy({ port: 8080, stats: 'minimal', hot: true, + proxy: { + '/graphql': 'http://localhost:8081', + }, }, plugins: [ new Dotenv({ diff --git a/dev.env b/dev.env index 0033ebb..636dd1a 100644 --- a/dev.env +++ b/dev.env @@ -1,2 +1,2 @@ NODE_ENV=development -GRAPHQL_URL=http://localhost:8081/graphql/employee +GRAPHQL_URL=/graphql/admin diff --git a/prod.env b/prod.env index 624e793..479c7bd 100644 --- a/prod.env +++ b/prod.env @@ -1,2 +1,2 @@ NODE_ENV=production -GRAPHQL_URL=/graphql/employee +GRAPHQL_URL=/graphql/admin diff --git a/src/core/api/graphql.client.ts b/src/core/api/graphql.client.ts new file mode 100644 index 0000000..61505b9 --- /dev/null +++ b/src/core/api/graphql.client.ts @@ -0,0 +1,4 @@ +import { GraphQLClient } from 'graphql-request'; + +const url = process.env.GRAPHQL_URL; +export const graphQLClient = new GraphQLClient(url); diff --git a/src/core/api/index.ts b/src/core/api/index.ts new file mode 100644 index 0000000..38f62bb --- /dev/null +++ b/src/core/api/index.ts @@ -0,0 +1 @@ +export * from './graphql.client'; diff --git a/src/core/router/index.ts b/src/core/router/index.ts index 0e1ad7f..8b4a4d1 100644 --- a/src/core/router/index.ts +++ b/src/core/router/index.ts @@ -1,2 +1,3 @@ export * from './router.component'; export * from './routes'; +export * from './route-params.vm'; diff --git a/src/core/router/route-params.vm.ts b/src/core/router/route-params.vm.ts new file mode 100644 index 0000000..66c2e6d --- /dev/null +++ b/src/core/router/route-params.vm.ts @@ -0,0 +1,3 @@ +export interface EditParams { + id: string; +} diff --git a/src/pods/employee-list/api/employee-list.api.ts b/src/pods/employee-list/api/employee-list.api.ts index 4cd5a38..40672f3 100644 --- a/src/pods/employee-list/api/employee-list.api.ts +++ b/src/pods/employee-list/api/employee-list.api.ts @@ -1,13 +1,40 @@ +import { graphQLClient } from 'core/api'; import { Employee } from './employee-list.api-model'; -import { mockEmployeeList } from './employee-list.mock-data'; -let employeeList = [...mockEmployeeList]; +interface GetEmployeeListResponse { + employees: Employee[]; +} export const getEmployeeList = async (): Promise => { - return employeeList; + const query = ` + query { + employees { + id + name + isActive + email + lastDateIncurred + } + } + `; + const { employees } = await graphQLClient.request( + query + ); + return employees; }; +interface DeleteEmployeeResponse { + deleteEmployee: boolean; +} + export const deleteEmployee = async (id: string): Promise => { - employeeList = employeeList.filter(e => e.id !== id); - return true; + const query = ` + mutation { + deleteEmployee(id: "${id}") + } + `; + const { deleteEmployee } = await graphQLClient.request< + DeleteEmployeeResponse + >(query); + return deleteEmployee; }; diff --git a/src/pods/employee-list/api/employee-list.mock-data.ts b/src/pods/employee-list/api/employee-list.mock-data.ts deleted file mode 100644 index b48dbfa..0000000 --- a/src/pods/employee-list/api/employee-list.mock-data.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { Employee } from './employee-list.api-model'; - -export const mockEmployeeList: Employee[] = [ - { - id: '1', - isActive: true, - name: 'Daniel Perez', - email: 'daniel.perez@empresa.com', - lastDateIncurred: '02/02/2020', - }, - { - id: '2', - isActive: true, - name: 'Jose Gomez', - email: 'jose.gomez@empresa.com', - lastDateIncurred: '05/02/2020', - }, - { - id: '3', - isActive: false, - name: 'Manuel Ruiz', - email: 'manuel.ruiz@empresa.com', - lastDateIncurred: '06/02/2020', - }, - { - id: '4', - isActive: true, - name: 'Ramón Gomez', - email: 'ramon.gomez@empresa.com', - lastDateIncurred: '02/05/2020', - }, - { - id: '5', - isActive: false, - name: 'María Lopez', - email: 'maria.lopez@empresa.com', - lastDateIncurred: '05/08/2020', - }, - { - id: '6', - isActive: true, - name: 'Manuel Ortiz', - email: 'manuel.ortiz@empresa.com', - lastDateIncurred: '06/06/2020', - }, - { - id: '7', - isActive: false, - name: 'David Martos', - email: 'david.martos@empresa.com', - lastDateIncurred: '14/08/2020', - }, - { - id: '8', - isActive: true, - name: 'Luz Roca', - email: 'luz.roca@empresa.com', - lastDateIncurred: '20/06/2020', - }, -]; diff --git a/src/pods/employee/api/employee.api-model.ts b/src/pods/employee/api/employee.api-model.ts index 87202d4..ea4a1ff 100644 --- a/src/pods/employee/api/employee.api-model.ts +++ b/src/pods/employee/api/employee.api-model.ts @@ -4,11 +4,15 @@ export interface Employee { email: string; isActive: boolean; temporalPassword?: string; - projects?: ProjectSummary[]; + projects?: EmployeeProject[]; } -export interface ProjectSummary { +export interface EmployeeProject { id: string; isAssigned?: boolean; - projectName: string; +} + +export interface Project { + id: string; + name: string; } diff --git a/src/pods/employee/api/employee.api.ts b/src/pods/employee/api/employee.api.ts index 252d864..91558b4 100644 --- a/src/pods/employee/api/employee.api.ts +++ b/src/pods/employee/api/employee.api.ts @@ -1,6 +1,85 @@ -import { Employee } from './employee.api-model'; -import { mockEmployee } from './employee.mock-data'; +import { Employee, EmployeeProject, Project } from './employee.api-model'; +import { graphQLClient } from 'core/api'; + +interface GetEmployeeResponse { + employee: Employee; +} export const getEmployeeById = async (id: string): Promise => { - return mockEmployee; + const query = ` + query { + employee(id: "${id}") { + id + name + isActive + email + projects { + id + isAssigned + } + } + } + `; + + const { employee } = await graphQLClient.request(query); + + return employee; +}; + +interface GetProjectListResponse { + projects: Project[]; +} + +export const getProjects = async (): Promise => { + const query = ` + query { + projects { + id + name + } + } + `; + const { projects } = await graphQLClient.request( + query + ); + return projects; +}; + +interface SaveEmployeeResponse { + saveEmployee: Employee; +} + +export const saveEmployee = async (employee: Employee): Promise => { + const query = ` + mutation($employee: EmployeeInput!) { + saveEmployee(employee: $employee) { + id + } + } + `; + + const { saveEmployee } = await graphQLClient.request( + query, + { + employee, + } + ); + + return saveEmployee.id; +}; + +export const saveEmployeeProjectList = async ( + id: string, + employeeProjectList: EmployeeProject[] +): Promise => { + const query = `mutation($employeeProjectList: [EmployeeProjectInput!]!) { + saveEmployeeProjectList( + id: "${id}", + employeeProjectList: $employeeProjectList, + ) { + id + } + }`; + + await graphQLClient.request(query, { employeeProjectList }); }; diff --git a/src/pods/employee/api/employee.mock-data.ts b/src/pods/employee/api/employee.mock-data.ts deleted file mode 100644 index 6eaf7b5..0000000 --- a/src/pods/employee/api/employee.mock-data.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { Employee, ProjectSummary } from './employee.api-model'; - -const mockProjectSummaryList: ProjectSummary[] = [ - { - id: '1', - isAssigned: true, - projectName: 'Mapfre', - }, - { - id: '2', - isAssigned: false, - projectName: 'Bankia', - }, - { - id: '3', - isAssigned: false, - projectName: 'Vacaciones', - }, - { - id: '4', - isAssigned: true, - projectName: 'Baja', - }, -]; - -export const mockEmployee: Employee = { - id: '1', - name: 'Prueba Nombre', - email: 'prueba@email.com', - isActive: true, - temporalPassword: 'admin', - projects: mockProjectSummaryList, -}; diff --git a/src/pods/employee/components/project-row.component.tsx b/src/pods/employee/components/project-row.component.tsx index af5c32f..f650ff0 100644 --- a/src/pods/employee/components/project-row.component.tsx +++ b/src/pods/employee/components/project-row.component.tsx @@ -5,19 +5,31 @@ import { CellComponent, } from 'common/components'; import Checkbox from '@material-ui/core/Checkbox'; -import { ProjectSummary } from '../employee.vm'; +import { EmployeeProject } from '../employee.vm'; -type Props = RowRendererProps; +interface RowProps extends RowRendererProps { + onChangeProject: (project: EmployeeProject) => void; +} -export const EmployeeRowComponent: React.FunctionComponent = ({ +export const EmployeeRowComponent: React.FunctionComponent = ({ row, + onChangeProject, }) => { return ( - + + onChangeProject({ + ...row, + isAssigned: checked, + }) + } + checked={row.isAssigned} + /> - {row.projectName} + {row.name} ); }; diff --git a/src/pods/employee/components/project.component.tsx b/src/pods/employee/components/project.component.tsx index 00df731..f9ac0bf 100644 --- a/src/pods/employee/components/project.component.tsx +++ b/src/pods/employee/components/project.component.tsx @@ -1,31 +1,52 @@ import React from 'react'; import { TableContainer, RowRendererProps } from 'common/components'; -import { ProjectSummary } from '../employee.vm'; +import { EmployeeProject } from '../employee.vm'; import { EmployeeRowComponent } from './project-row.component'; import { CommandFooterComponent } from 'common-app/command-footer'; interface Props { - projectSummaryList: ProjectSummary[]; - className?: string; + employeeProjectList: EmployeeProject[]; + onSave: (project: EmployeeProject[]) => void; onCancel: () => void; + className?: string; } export const ProjectComponent: React.FunctionComponent = ({ - projectSummaryList, - className, + employeeProjectList, + onSave, onCancel, + className, }) => { + const [projectList, setProjectList] = React.useState( + employeeProjectList + ); + + React.useEffect(() => { + setProjectList(employeeProjectList); + }, [employeeProjectList]); + + const handleChangeProject = (id: string) => (project: EmployeeProject) => { + const updateProjectList = projectList.map(p => (p.id === id ? project : p)); + setProjectList(updateProjectList); + }; + + const handleSave = () => onSave(projectList); return ( <> ) => ( - + rowRenderer={(rowProps: RowRendererProps) => ( + )} + enablePagination={true} + pageSize={5} /> - + ); }; diff --git a/src/pods/employee/employee.component.tsx b/src/pods/employee/employee.component.tsx index 62591ea..6c54de4 100644 --- a/src/pods/employee/employee.component.tsx +++ b/src/pods/employee/employee.component.tsx @@ -6,14 +6,15 @@ import { } from 'common/components'; import AppBar from '@material-ui/core/AppBar'; import { DataComponent, ProjectComponent, ReportComponent } from './components'; -import { Employee, Report } from './employee.vm'; +import { Employee, Report, EmployeeProject } from './employee.vm'; import * as classes from './employee.styles'; interface Props { employee: Employee; isEditMode: boolean; report: Report; - onSave: (employee: Employee) => void; + onSaveEmployee: (employee: Employee) => void; + onSaveProjectSelection: (project: EmployeeProject[]) => void; onCancel: () => void; onGenerateExcel: (report: Report) => void; } @@ -22,7 +23,8 @@ export const EmployeeComponent: React.FunctionComponent = ({ employee, isEditMode, report, - onSave, + onSaveEmployee, + onSaveProjectSelection, onCancel, onGenerateExcel, }) => { @@ -40,15 +42,16 @@ export const EmployeeComponent: React.FunctionComponent = ({ diff --git a/src/pods/employee/employee.container.tsx b/src/pods/employee/employee.container.tsx index 876923a..c9e3342 100644 --- a/src/pods/employee/employee.container.tsx +++ b/src/pods/employee/employee.container.tsx @@ -5,27 +5,45 @@ import { Report, createEmptyEmployee, createEmptyReport, + EmployeeProject, } from './employee.vm'; import { useSnackbarContext } from 'common/components'; import { trackPromise } from 'react-promise-tracker'; -import { getEmployeeById } from './api'; -import { mapEmployeeFromApiToVm } from './employee.mappers'; +import { + getEmployeeById, + saveEmployee, + saveEmployeeProjectList, + getProjects, +} from './api'; +import { + mapEmployeeFromApiToVm, + mapEmployeeFromVmToApi, + mapEmployeeProjectListFromVmToApi, +} from './employee.mappers'; import { useParams } from 'react-router-dom'; import { isEditModeHelper } from 'common/helpers'; +import { routes, EditParams } from 'core/router'; +import { useHistory } from 'react-router'; export const EmployeeContainer: React.FunctionComponent = () => { - const { id } = useParams(); + const params = useParams(); const [employee, setEmployee] = React.useState( createEmptyEmployee() ); const [isEditMode, setIsEditMode] = React.useState(false); const [report, setReport] = React.useState(createEmptyReport()); const { showMessage } = useSnackbarContext(); + const history = useHistory(); const onLoadEmployee = async () => { try { - const apiEmployee = await trackPromise(getEmployeeById(id)); - const viewModelEmployee = mapEmployeeFromApiToVm(apiEmployee); + const [apiProjects, apiEmployee] = await trackPromise( + Promise.all([getProjects(), getEmployeeById(params.id)]) + ); + const viewModelEmployee = mapEmployeeFromApiToVm( + apiEmployee, + apiProjects + ); setEmployee(viewModelEmployee); } catch (error) { error && @@ -33,12 +51,50 @@ export const EmployeeContainer: React.FunctionComponent = () => { } }; - const handleSave = (employee: Employee) => { - console.log('Guardado'); + const handleSuccessSaveEmployee = (id: string, newEmployee: Employee) => { + if (id) { + showMessage('Empleado guardado con éxito', 'success'); + setEmployee(newEmployee); + history.push(routes.editEmployee(id)); + } else { + showMessage('Ha ocurrido un error al guardar el empleado', 'error'); + } + }; + + const handleSaveEmployee = async (employee: Employee) => { + try { + const apiEmployee = mapEmployeeFromVmToApi(employee); + const id = await trackPromise(saveEmployee(apiEmployee)); + handleSuccessSaveEmployee(id, employee); + } catch (error) { + error && showMessage(error.message, 'error'); + } + }; + + const handleSaveProjectSelection = async ( + employeeSummary: EmployeeProject[] + ) => { + if (params.id) { + try { + const apiProjectSummary = mapEmployeeProjectListFromVmToApi( + employeeSummary + ); + await trackPromise( + saveEmployeeProjectList(params.id, apiProjectSummary) + ); + setEmployee({ + ...employee, + projects: employeeSummary, + }); + showMessage('Se actualizó con éxito', 'success'); + } catch (error) { + error && showMessage('Ha ocurrido un error al guardar', 'error'); + } + } }; const handleCancel = () => { - history.back(); + history.goBack(); }; const handleGenerateExcel = (report: Report) => { @@ -47,7 +103,7 @@ export const EmployeeContainer: React.FunctionComponent = () => { }; React.useEffect(() => { - const isEditMode = isEditModeHelper(id); + const isEditMode = isEditModeHelper(params.id); setIsEditMode(isEditMode); if (isEditMode) { onLoadEmployee(); @@ -59,7 +115,8 @@ export const EmployeeContainer: React.FunctionComponent = () => { employee={employee} isEditMode={isEditMode} report={report} - onSave={handleSave} + onSaveEmployee={handleSaveEmployee} + onSaveProjectSelection={handleSaveProjectSelection} onCancel={handleCancel} onGenerateExcel={handleGenerateExcel} /> diff --git a/src/pods/employee/employee.mappers.spec.ts b/src/pods/employee/employee.mappers.spec.ts index 8085341..01d3fcf 100644 --- a/src/pods/employee/employee.mappers.spec.ts +++ b/src/pods/employee/employee.mappers.spec.ts @@ -1,120 +1,346 @@ -import { mapEmployeeFromApiToVm } from './employee.mappers'; +import { + mapEmployeeFromApiToVm, + mapEmployeeFromVmToApi, + mapEmployeeProjectListFromVmToApi, +} from './employee.mappers'; import * as apiModel from './api/employee.api-model'; import * as viewModel from './employee.vm'; describe('./pods/employee/employee.mappers', () => { - it('should return empty employee when feeding null value', () => { - // Arrange - const employee = null; + describe('mapEmployeeFromApiToVm', () => { + it('should return empty employee when feeding null value', () => { + // Arrange + const employee = null; + const projects = null; - // Act - const result = mapEmployeeFromApiToVm(employee); + // Act + const result = mapEmployeeFromApiToVm(employee, projects); - // Assert - expect(result).toEqual(viewModel.createEmptyEmployee()); - }); + // Assert + expect(result).toEqual(viewModel.createEmptyEmployee()); + }); - it('should return empty employee when feeding undefined value', () => { - // Arrange - const employee = undefined; + it('should return empty employee when feeding undefined value', () => { + // Arrange + const employee = undefined; + const projects = undefined; - // Act - const result = mapEmployeeFromApiToVm(employee); + // Act + const result = mapEmployeeFromApiToVm(employee, projects); - // Assert - expect(result).toEqual(viewModel.createEmptyEmployee()); - }); + // Assert + expect(result).toEqual(viewModel.createEmptyEmployee()); + }); + + it('should return expected result but feeding null project list', () => { + // Arrange + const employee: apiModel.Employee = { + id: 'test id', + name: 'test name', + email: 'test@email.com', + isActive: true, + temporalPassword: 'test password', + projects: null, + }; + + const projects: apiModel.Project[] = [ + { + id: '1', + name: 'project test 1', + }, + { + id: '2', + name: 'project test 2', + }, + ]; + + const expectedResult: viewModel.Employee = { + id: 'test id', + name: 'test name', + email: 'test@email.com', + isActive: true, + temporalPassword: 'test password', + projects: [ + { + id: '1', + name: 'project test 1', + isAssigned: false, + }, + { + id: '2', + name: 'project test 2', + isAssigned: false, + }, + ], + }; + + // Act + const result = mapEmployeeFromApiToVm(employee, projects); + + // Assert + expect(result).toEqual(expectedResult); + }); + + it('should return expected result but feeding undefined project list', () => { + // Arrange + const employee: apiModel.Employee = { + id: 'test id', + name: 'test name', + email: 'test@email.com', + isActive: true, + temporalPassword: 'test password', + projects: undefined, + }; + + const expectedResult: viewModel.Employee = { + id: 'test id', + name: 'test name', + email: 'test@email.com', + isActive: true, + temporalPassword: 'test password', + projects: [ + { + id: '1', + name: 'project test 1', + isAssigned: false, + }, + { + id: '2', + name: 'project test 2', + isAssigned: false, + }, + ], + }; + + const projects: apiModel.Project[] = [ + { + id: '1', + name: 'project test 1', + }, + { + id: '2', + name: 'project test 2', + }, + ]; + + // Act + const result = mapEmployeeFromApiToVm(employee, projects); + + // Assert + expect(result).toEqual(expectedResult); + }); + + it('should return expected result feeding correct values', () => { + // Arrange + const employee: apiModel.Employee = { + id: 'test id', + name: 'test name', + email: 'test@email.com', + isActive: true, + temporalPassword: 'test password', + projects: [ + { + id: '1', + isAssigned: true, + }, + { + id: '2', + isAssigned: false, + }, + ], + }; + + const projects: apiModel.Project[] = [ + { + id: '1', + name: 'project test 1', + }, + { + id: '2', + name: 'project test 2', + }, + ]; + + const expectedResult: viewModel.Employee = { + id: 'test id', + name: 'test name', + email: 'test@email.com', + isActive: true, + temporalPassword: 'test password', + projects: [ + { + id: '1', + isAssigned: true, + name: 'project test 1', + }, + { + id: '2', + isAssigned: false, + name: 'project test 2', + }, + ], + }; + + // Act + const result = mapEmployeeFromApiToVm(employee, projects); - it('should return expected result but feeding null project list', () => { - // Arrange - const employee: apiModel.Employee = { - id: 'test id', - name: 'test name', - email: 'test@email.com', - isActive: true, - temporalPassword: 'test password', - projects: null, - }; - - const expectedResult: viewModel.Employee = { - id: 'test id', - name: 'test name', - email: 'test@email.com', - isActive: true, - temporalPassword: 'test password', - projects: [], - }; - - // Act - const result = mapEmployeeFromApiToVm(employee); - - // Assert - expect(result).toEqual(expectedResult); + // Assert + expect(result).toEqual(expectedResult); + }); }); - it('should return expected result but feeding undefined project list', () => { - // Arrange - const employee: apiModel.Employee = { - id: 'test id', - name: 'test name', - email: 'test@email.com', - isActive: true, - temporalPassword: 'test password', - projects: undefined, - }; - - const expectedResult: viewModel.Employee = { - id: 'test id', - name: 'test name', - email: 'test@email.com', - isActive: true, - temporalPassword: 'test password', - projects: [], - }; - - // Act - const result = mapEmployeeFromApiToVm(employee); - - // Assert - expect(result).toEqual(expectedResult); + describe('mapEmployeeFromVmToApi', () => { + it('should return empty employee when feeding null', () => { + // Arrange + const employee = null; + + // Act + const result = mapEmployeeFromVmToApi(employee); + + //Assert + expect(result).toEqual(viewModel.createEmptyEmployee()); + }); + + it('should return empty employee when feeding undefined', () => { + // Arrange + const employee = undefined; + + // Act + const result = mapEmployeeFromVmToApi(employee); + + //Assert + expect(result).toEqual(viewModel.createEmptyEmployee()); + }); + + it('should return expected value when feeding employee, and empty project list', () => { + // Arrange + const employee: viewModel.Employee = { + id: '1', + isActive: true, + name: 'test name', + email: 'test@email.com', + temporalPassword: 'test', + projects: [], + }; + + const expectedValue: apiModel.Employee = { + id: '1', + isActive: true, + name: 'test name', + email: 'test@email.com', + temporalPassword: 'test', + }; + + // Act + const result = mapEmployeeFromVmToApi(employee); + + //Assert + expect(result).toEqual(expectedValue); + }); + + it('should return expected value when feeding employee, and project list', () => { + // Arrange + const employee: viewModel.Employee = { + id: '1', + isActive: true, + name: 'test name', + email: 'test@email.com', + temporalPassword: 'test', + projects: [ + { + id: '1', + isAssigned: true, + name: 'test name', + }, + { + id: '2', + isAssigned: false, + name: 'test name', + }, + ], + }; + + const expectedValue: apiModel.Employee = { + id: '1', + isActive: true, + name: 'test name', + email: 'test@email.com', + temporalPassword: 'test', + }; + + // Act + const result = mapEmployeeFromVmToApi(employee); + + //Assert + expect(result).toEqual(expectedValue); + }); }); - it('should return expected result feeding correct values', () => { - // Arrange - const employee: apiModel.Employee = { - id: 'test id', - name: 'test name', - email: 'test@email.com', - isActive: true, - temporalPassword: 'test password', - projects: [ + describe('mapEmployeeProjectListFromVmToApi', () => { + it('should return emtpy array when feeding employee project null', () => { + // Arrange + const employeeProjectList = null; + + // Act + const result = mapEmployeeProjectListFromVmToApi(employeeProjectList); + + // Assert + expect(result).toEqual([]); + }); + + it('should return emtpy array when feeding employee project undefined', () => { + // Arrange + const employeeProjectList = undefined; + + // Act + const result = mapEmployeeProjectListFromVmToApi(employeeProjectList); + + // Assert + expect(result).toEqual([]); + }); + + it('should return emtpy array when feeding employee project empty array', () => { + // Arrange + const employeeProjectList = []; + + // Act + const result = mapEmployeeProjectListFromVmToApi(employeeProjectList); + + // Assert + expect(result).toEqual([]); + }); + + it('should return expected result when feeding employee project list', () => { + // Arrange + const employeeProjectList: viewModel.EmployeeProject[] = [ { - id: 'test id', - projectName: 'test employee name', + id: '1', + name: 'test name', isAssigned: true, }, - ], - }; - - const expectedResult: viewModel.Employee = { - id: 'test id', - name: 'test name', - email: 'test@email.com', - isActive: true, - temporalPassword: 'test password', - projects: [ { - id: 'test id', - projectName: 'test employee name', + id: '2', + name: 'test name', + isAssigned: false, + }, + ]; + + const expectedResult: apiModel.EmployeeProject[] = [ + { + id: '1', isAssigned: true, }, - ], - }; + { + id: '2', + isAssigned: false, + }, + ]; - // Act - const result = mapEmployeeFromApiToVm(employee); + // Act + const result = mapEmployeeProjectListFromVmToApi(employeeProjectList); - // Assert - expect(result).toEqual(expectedResult); + // Assert + expect(result).toEqual(expectedResult); + }); }); }); diff --git a/src/pods/employee/employee.mappers.ts b/src/pods/employee/employee.mappers.ts index 3464896..2f7d128 100644 --- a/src/pods/employee/employee.mappers.ts +++ b/src/pods/employee/employee.mappers.ts @@ -3,23 +3,60 @@ import * as apiModel from './api/employee.api-model'; import * as viewModel from './employee.vm'; const mapProjectSummaryFromApiToVm = ( - projectSummary: apiModel.ProjectSummary -): viewModel.ProjectSummary => ({ - ...projectSummary, -}); + project: apiModel.Project, + employeeProjectList: apiModel.EmployeeProject[] +): viewModel.EmployeeProject => { + const employeeProject = employeeProjectList + ? employeeProjectList.find(ep => ep.id === project.id) + : viewModel.createEmptyEmployeeProject(); + return { + ...project, + isAssigned: employeeProject?.isAssigned, + }; +}; const mapProjectSummaryListFromApiToVm = ( - projectSummary: apiModel.ProjectSummary[] -): viewModel.ProjectSummary[] => - mapToCollection(projectSummary, ps => mapProjectSummaryFromApiToVm(ps)); + project: apiModel.Project[], + employeeProject: apiModel.EmployeeProject[] +): viewModel.EmployeeProject[] => + mapToCollection(project, p => + mapProjectSummaryFromApiToVm(p, employeeProject) + ); export const mapEmployeeFromApiToVm = ( - employee: apiModel.Employee + employee: apiModel.Employee, + projects: apiModel.Project[] ): viewModel.Employee => { return Boolean(employee) ? { ...employee, - projects: mapProjectSummaryListFromApiToVm(employee.projects), + projects: mapProjectSummaryListFromApiToVm(projects, employee.projects), } : viewModel.createEmptyEmployee(); }; + +export const mapEmployeeFromVmToApi = ( + employee: viewModel.Employee +): apiModel.Employee => { + return Boolean(employee) + ? { + id: employee.id, + name: employee.name, + email: employee.email, + isActive: employee.isActive, + temporalPassword: employee.temporalPassword, + } + : viewModel.createEmptyEmployee(); +}; + +const mapEmployeeProjectFromVmToApi = ( + employeeProject: viewModel.EmployeeProject +): apiModel.EmployeeProject => ({ + id: employeeProject.id, + isAssigned: employeeProject.isAssigned, +}); + +export const mapEmployeeProjectListFromVmToApi = ( + employeeProjectList: viewModel.EmployeeProject[] +): apiModel.EmployeeProject[] => + mapToCollection(employeeProjectList, mapEmployeeProjectFromVmToApi); diff --git a/src/pods/employee/employee.vm.ts b/src/pods/employee/employee.vm.ts index 5835537..f8229c6 100644 --- a/src/pods/employee/employee.vm.ts +++ b/src/pods/employee/employee.vm.ts @@ -4,13 +4,13 @@ export interface Employee { email: string; isActive: boolean; temporalPassword?: string; - projects: ProjectSummary[]; + projects: EmployeeProject[]; } -export interface ProjectSummary { +export interface EmployeeProject { id: string; isAssigned?: boolean; - projectName: string; + name: string; } export interface Report { @@ -31,3 +31,9 @@ export const createEmptyReport = (): Report => ({ month: '', year: '', }); + +export const createEmptyEmployeeProject = (): EmployeeProject => ({ + id: '', + name: '', + isAssigned: false, +}); diff --git a/src/pods/project-list/api/project-list.api.ts b/src/pods/project-list/api/project-list.api.ts index 801356b..3dcbd04 100644 --- a/src/pods/project-list/api/project-list.api.ts +++ b/src/pods/project-list/api/project-list.api.ts @@ -1,13 +1,41 @@ +import { graphQLClient } from 'core/api'; import { Project } from './project-list.api-model'; -import { mockProjectList } from './project-list.mock-data'; -let projectList = [...mockProjectList]; +interface GetProjectListReponse { + projects: Project[]; +} export const getProjectList = async (): Promise => { - return projectList; + const query = ` + query { + projects { + id + isActive + code + name + lastDateIncurred + creationDate + } + } + `; + const { projects } = await graphQLClient.request( + query + ); + return projects; }; +interface DeleteProjectResponse { + deleteProject: boolean; +} + export const deleteProject = async (id: string): Promise => { - projectList = projectList.filter(p => p.id !== id); - return true; + const query = ` + mutation { + deleteProject(id: "${id}") + } + `; + const { deleteProject } = await graphQLClient.request( + query + ); + return deleteProject; }; diff --git a/src/pods/project-list/api/project-list.mock-data.ts b/src/pods/project-list/api/project-list.mock-data.ts deleted file mode 100644 index 243cf29..0000000 --- a/src/pods/project-list/api/project-list.mock-data.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { Project } from './project-list.api-model'; - -export const mockProjectList: Project[] = [ - { - id: '1', - isActive: true, - code: '23212', - name: 'Bankia', - lastDateIncurred: '02/02/2020', - creationDate: '01/08/2018', - }, - { - id: '2', - isActive: true, - code: '4323', - name: 'Mapfre', - lastDateIncurred: '05/02/2020', - creationDate: '01/04/2018', - }, - { - id: '3', - isActive: true, - code: '002', - name: 'Vacaciones', - lastDateIncurred: '05/02/2020', - creationDate: '01/04/2018', - }, - { - id: '4', - isActive: true, - code: '003', - name: 'Baja Médica', - lastDateIncurred: '05/03/2018', - creationDate: '01/05/2019', - }, - { - id: '5', - isActive: false, - code: '2586', - name: 'Proyecto interno', - lastDateIncurred: '05/08/2020', - creationDate: '01/10/2018', - }, - { - id: '6', - isActive: false, - code: '3025', - name: 'BBVA', - lastDateIncurred: '06/05/2020', - creationDate: '01/03/2019', - }, - { - id: '7', - isActive: false, - code: '8563', - name: 'Baja Médica', - lastDateIncurred: '02/08/2018', - creationDate: '01/11/2020', - }, - { - id: '8', - isActive: true, - code: '4125', - name: 'Microsoft España', - lastDateIncurred: '11/10/2018', - creationDate: '01/07/2020', - }, -]; diff --git a/src/pods/project/api/employee.api.ts b/src/pods/project/api/employee.api.ts deleted file mode 100644 index 3d08210..0000000 --- a/src/pods/project/api/employee.api.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { Project } from './project.api-model'; -import { mockProject } from './project.mock-data'; - -export const getProjectById = async (id: string): Promise => { - return mockProject; -}; diff --git a/src/pods/project/api/index.ts b/src/pods/project/api/index.ts index aea94bb..e38a565 100644 --- a/src/pods/project/api/index.ts +++ b/src/pods/project/api/index.ts @@ -1 +1 @@ -export * from './employee.api'; +export * from './project.api'; diff --git a/src/pods/project/api/project.api-model.ts b/src/pods/project/api/project.api-model.ts index 1927eff..1b735cb 100644 --- a/src/pods/project/api/project.api-model.ts +++ b/src/pods/project/api/project.api-model.ts @@ -4,11 +4,15 @@ export interface Project { externalId?: string; comments?: string; isActive: boolean; - employees: EmployeeSummary[]; + employees?: ProjectEmployee[]; } -export interface EmployeeSummary { +export interface ProjectEmployee { id: string; isAssigned?: boolean; - employeeName: string; +} + +export interface Employee { + id: string; + name: string; } diff --git a/src/pods/project/api/project.api.ts b/src/pods/project/api/project.api.ts new file mode 100644 index 0000000..3c6f23c --- /dev/null +++ b/src/pods/project/api/project.api.ts @@ -0,0 +1,83 @@ +import { Project, Employee, ProjectEmployee } from './project.api-model'; +import { graphQLClient } from 'core/api'; + +interface GetProjectResponse { + project: Project; +} + +export const getProjectById = async (id: string): Promise => { + const query = ` + query { + project(id: "${id}") { + id + name + isActive + externalId + comments + employees { + id + isAssigned + } + } + } + `; + const { project } = await graphQLClient.request(query); + + return project; +}; + +interface GetEmployeeListResponse { + employees: Employee[]; +} + +export const getEmployees = async (): Promise => { + const query = ` + query { + employees { + id + name + } + } + `; + const { employees } = await graphQLClient.request( + query + ); + return employees; +}; + +interface SaveProjectResponse { + saveProject: Project; +} + +export const saveProject = async (project: Project): Promise => { + const query = ` + mutation($project: ProjectInput!) { + saveProject(project: $project) { + id + } + } + `; + + const { saveProject } = await graphQLClient.request( + query, + { + project, + } + ); + + return saveProject.id; +}; + +export const saveProjectEmployeeList = async ( + id: string, + projectEmployeeList: ProjectEmployee[] +): Promise => { + const query = `mutation($projectEmployeeList: [ProjectEmployeeInput!]!) { + saveProjectEmployeeList(id: "${id}", projectEmployeeList: $projectEmployeeList,) { + id + } + } + `; + + await graphQLClient.request(query, { projectEmployeeList }); +}; diff --git a/src/pods/project/api/project.mock-data.ts b/src/pods/project/api/project.mock-data.ts deleted file mode 100644 index aa5e4c6..0000000 --- a/src/pods/project/api/project.mock-data.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { Project, EmployeeSummary } from './project.api-model'; - -const mockEmployeeSummaryList: EmployeeSummary[] = [ - { - id: '1', - employeeName: 'Daniel Perez', - isAssigned: true, - }, - { - id: '2', - employeeName: 'Jose Sanchez', - isAssigned: false, - }, - { - id: '3', - employeeName: 'Javier Benitez', - isAssigned: false, - }, - { - id: '4', - employeeName: 'María Peña', - isAssigned: true, - }, -]; - -export const mockProject: Project = { - id: '1', - name: 'Nombre', - isActive: true, - comments: 'Comentario', - externalId: '1234', - employees: mockEmployeeSummaryList, -}; diff --git a/src/pods/project/components/data.component.tsx b/src/pods/project/components/data.component.tsx index df68d1c..f09dadb 100644 --- a/src/pods/project/components/data.component.tsx +++ b/src/pods/project/components/data.component.tsx @@ -3,6 +3,7 @@ import { Formik, Form } from 'formik'; import { TextFieldComponent, CheckboxComponent } from 'common/components'; import { CommandFooterComponent } from '../../../common-app/command-footer'; import { Project } from '../project.vm'; +import { formValidation } from './data.validations'; interface Props { project: Project; @@ -18,7 +19,12 @@ export const DataComponent: React.FunctionComponent = ({ className, }) => { return ( - + {() => (
; +interface RowProps extends RowRendererProps { + onChangeEmployee: (employee: ProjectEmployee) => void; +} -export const ProjectRowComponent: React.FunctionComponent = ({ +export const ProjectRowComponent: React.FunctionComponent = ({ row, + onChangeEmployee, }) => { return ( - + + onChangeEmployee({ + ...row, + isAssigned: checked, + }) + } + checked={row.isAssigned} + /> - {row.employeeName} + {row.name} ); }; diff --git a/src/pods/project/components/employee.component.tsx b/src/pods/project/components/employee.component.tsx index 6c2ec95..42b859d 100644 --- a/src/pods/project/components/employee.component.tsx +++ b/src/pods/project/components/employee.component.tsx @@ -1,31 +1,54 @@ import React from 'react'; import { TableContainer, RowRendererProps } from 'common/components'; -import { EmployeeSummary } from '../project.vm'; +import { ProjectEmployee } from '../project.vm'; import { CommandFooterComponent } from 'common-app/command-footer'; import { ProjectRowComponent } from './employee-row.component'; interface Props { - employeeSummaryList: EmployeeSummary[]; + projectEmployeeList: ProjectEmployee[]; + onSave: (projectEmployeeList: ProjectEmployee[]) => void; onCancel: () => void; className: string; } export const EmployeeComponent: React.FunctionComponent = ({ - employeeSummaryList, + projectEmployeeList, + onSave, onCancel, className, }) => { + const [employeeList, setProjectList] = React.useState( + projectEmployeeList + ); + + React.useEffect(() => { + setProjectList(projectEmployeeList); + }, [projectEmployeeList]); + + const handleChangeEmployee = (id: string) => (employee: ProjectEmployee) => { + const updateEmployeeList = employeeList.map(e => + e.id === id ? employee : e + ); + setProjectList(updateEmployeeList); + }; + + const handleSave = () => onSave(employeeList); return ( <> ) => ( - + rowRenderer={(rowProps: RowRendererProps) => ( + )} + enablePagination={true} + pageSize={5} /> - + ); }; diff --git a/src/pods/project/components/report.component.tsx b/src/pods/project/components/report.component.tsx index 263945b..71415cb 100644 --- a/src/pods/project/components/report.component.tsx +++ b/src/pods/project/components/report.component.tsx @@ -5,25 +5,35 @@ import { monthList } from 'common/constants'; import { CommandFooterComponent } from 'common-app/command-footer'; import { cx } from 'emotion'; import * as classes from './report.styles'; +import { formValidation } from './report.validations'; +import { Report } from '../project.vm'; interface Props { + report: Report; onCancel: () => void; className: string; + onGenerateExcel: (report: Report) => void; } export const ReportComponent: React.FunctionComponent = ({ + report, + onGenerateExcel, onCancel, className, }) => { return ( - + {() => ( = ({ name: '2020', }, ]} - disabled className={classes.year} /> void; + report: Report; + onSaveProject: (project: Project) => void; + onSaveEmployeeSelection: (employeeProjectList: ProjectEmployee[]) => void; onCancel: () => void; + onGenerateExcel: (report: Report) => void; } export const ProjectComponent: React.FunctionComponent = ({ isEditMode, project, - onSave, + report, + onSaveProject, + onSaveEmployeeSelection, onCancel, + onGenerateExcel, }) => { const [tab, setTab] = React.useState(0); return ( @@ -40,19 +46,25 @@ export const ProjectComponent: React.FunctionComponent = ({ - + ); diff --git a/src/pods/project/project.container.tsx b/src/pods/project/project.container.tsx index 18d9ee7..0dc6b45 100644 --- a/src/pods/project/project.container.tsx +++ b/src/pods/project/project.container.tsx @@ -2,22 +2,43 @@ import React from 'react'; import { ProjectComponent } from './project.component'; import { useParams } from 'react-router-dom'; import { useSnackbarContext } from 'common/components'; -import { getProjectById } from './api'; +import { + getProjectById, + getEmployees, + saveProject, + saveProjectEmployeeList, +} from './api'; import { trackPromise } from 'react-promise-tracker'; -import { mapProjectFromApiToVm } from './project.mapper'; -import { Project, createEmptyProject } from './project.vm'; +import { + mapProjectFromApiToVm, + mapProjectFromVmToApi, + mapProjectEmployeeListFromVmToApi, +} from './project.mappers'; +import { + Project, + createEmptyProject, + Report, + createEmptyReport, + ProjectEmployee, +} from './project.vm'; import { isEditModeHelper } from 'common/helpers'; +import { useHistory } from 'react-router'; +import { routes, EditParams } from 'core/router'; export const ProjectContainer: React.FunctionComponent = () => { - const { id } = useParams(); + const params = useParams(); const [project, setProject] = React.useState(createEmptyProject()); const [isEditMode, setIsEditMode] = React.useState(false); + const [report, setReport] = React.useState(createEmptyReport()); const { showMessage } = useSnackbarContext(); + const history = useHistory(); const onLoadProject = async () => { try { - const apiProject = await trackPromise(getProjectById(id)); - const viewModelProject = mapProjectFromApiToVm(apiProject); + const [apiEmployees, apiProject] = await trackPromise( + Promise.all([getEmployees(), getProjectById(params.id)]) + ); + const viewModelProject = mapProjectFromApiToVm(apiProject, apiEmployees); setProject(viewModelProject); } catch (error) { error && @@ -25,16 +46,56 @@ export const ProjectContainer: React.FunctionComponent = () => { } }; - const handleSave = (Project: Project) => { - console.log('Guardado'); + const handleSuccessSaveProject = (id: string, newProject: Project) => { + if (id) { + showMessage('Proyecto guardado con éxito', 'success'); + setProject(newProject); + history.push(routes.editProject(id)); + } else { + showMessage('Ha ocurrido un error al guardar el empleado', 'error'); + } + }; + + const handleSaveProject = async (project: Project) => { + try { + const apiProject = mapProjectFromVmToApi(project); + const id = await trackPromise(saveProject(apiProject)); + handleSuccessSaveProject(id, project); + } catch (error) { + error && showMessage(error.message, 'error'); + } + }; + + const handleSaveEmployeeSelection = async ( + proyectEmployeeList: ProjectEmployee[] + ) => { + if (params.id) { + try { + const apiEmployeeProjectList = mapProjectEmployeeListFromVmToApi( + proyectEmployeeList + ); + await trackPromise( + saveProjectEmployeeList(params.id, apiEmployeeProjectList) + ); + setProject({ ...project, employees: proyectEmployeeList }); + showMessage('Se actualizó con éxito', 'success'); + } catch (error) { + error && showMessage('Ha ocurrido un error al guardar', 'error'); + } + } }; const handleCancel = () => { - history.back(); + history.goBack(); + }; + + const handleGenerateExcel = (report: Report) => { + // Pending to create real implementation + console.log('Excel creado'); }; React.useEffect(() => { - const isEditMode = isEditModeHelper(id); + const isEditMode = isEditModeHelper(params.id); setIsEditMode(isEditMode); if (isEditMode) { onLoadProject(); @@ -45,8 +106,11 @@ export const ProjectContainer: React.FunctionComponent = () => { ); }; diff --git a/src/pods/project/project.mapper.ts b/src/pods/project/project.mapper.ts deleted file mode 100644 index 68561f6..0000000 --- a/src/pods/project/project.mapper.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { mapToCollection } from 'common/mappers'; -import * as apiModel from './api/project.api-model'; -import * as viewModel from './project.vm'; - -const mapEmployeeSummaryFromApiToVm = ( - employeeSummary: apiModel.EmployeeSummary -): viewModel.EmployeeSummary => ({ - ...employeeSummary, -}); - -const mapEmployeeSummaryListFromApiToVm = ( - employeeSummary: apiModel.EmployeeSummary[] -): viewModel.EmployeeSummary[] => - mapToCollection(employeeSummary, es => mapEmployeeSummaryFromApiToVm(es)); - -export const mapProjectFromApiToVm = ( - project: apiModel.Project -): viewModel.Project => { - return Boolean(project) - ? { - ...project, - employees: mapEmployeeSummaryListFromApiToVm(project.employees), - } - : viewModel.createEmptyProject(); -}; diff --git a/src/pods/project/project.mappers.spec.ts b/src/pods/project/project.mappers.spec.ts new file mode 100644 index 0000000..ef5781e --- /dev/null +++ b/src/pods/project/project.mappers.spec.ts @@ -0,0 +1,346 @@ +import { + mapProjectFromApiToVm, + mapProjectFromVmToApi, + mapProjectEmployeeListFromVmToApi, +} from './project.mappers'; +import * as apiModel from './api/project.api-model'; +import * as viewModel from './project.vm'; + +describe('./pods/project/project.mappers', () => { + describe('mapProjectFromApiToVm', () => { + it('should return empty project when feeding null value', () => { + // Arrange + const project = null; + const employees = null; + + // Act + const result = mapProjectFromApiToVm(project, employees); + + // Assert + expect(result).toEqual(viewModel.createEmptyProject()); + }); + + it('should return empty project when feeding undefined value', () => { + // Arrange + const project = undefined; + const employees = undefined; + + // Act + const result = mapProjectFromApiToVm(project, employees); + + // Assert + expect(result).toEqual(viewModel.createEmptyProject()); + }); + + it('should return expected result but feeding null employee list', () => { + // Arrange + const project: apiModel.Project = { + id: 'test id', + name: 'test name', + isActive: true, + comments: 'Test comments', + externalId: '123', + employees: null, + }; + + const employees: apiModel.Employee[] = [ + { + id: '1', + name: 'employee test 1', + }, + { + id: '2', + name: 'employee test 2', + }, + ]; + + const expectedResult: viewModel.Project = { + id: 'test id', + name: 'test name', + isActive: true, + comments: 'Test comments', + externalId: '123', + employees: [ + { + id: '1', + name: 'employee test 1', + isAssigned: false, + }, + { + id: '2', + name: 'employee test 2', + isAssigned: false, + }, + ], + }; + + // Act + const result = mapProjectFromApiToVm(project, employees); + + // Assert + expect(result).toEqual(expectedResult); + }); + + it('should return expected result but feeding undefined employee list', () => { + // Arrange + const project: apiModel.Project = { + id: 'test id', + name: 'test name', + isActive: true, + comments: 'Test comments', + externalId: '123', + employees: undefined, + }; + + const expectedResult: viewModel.Project = { + id: 'test id', + name: 'test name', + isActive: true, + comments: 'Test comments', + externalId: '123', + employees: [ + { + id: '1', + name: 'employee test 1', + isAssigned: false, + }, + { + id: '2', + name: 'employee test 2', + isAssigned: false, + }, + ], + }; + + const employees: apiModel.Employee[] = [ + { + id: '1', + name: 'employee test 1', + }, + { + id: '2', + name: 'employee test 2', + }, + ]; + + // Act + const result = mapProjectFromApiToVm(project, employees); + + // Assert + expect(result).toEqual(expectedResult); + }); + + it('should return expected result feeding correct values', () => { + // Arrange + const project: apiModel.Project = { + id: 'test id', + name: 'test name', + isActive: true, + comments: 'Test comments', + externalId: '123', + employees: [ + { + id: '1', + isAssigned: true, + }, + { + id: '2', + isAssigned: false, + }, + ], + }; + + const employees: apiModel.Employee[] = [ + { + id: '1', + name: 'employee test 1', + }, + { + id: '2', + name: 'employee test 2', + }, + ]; + + const expectedResult: viewModel.Project = { + id: 'test id', + name: 'test name', + isActive: true, + comments: 'Test comments', + externalId: '123', + employees: [ + { + id: '1', + isAssigned: true, + name: 'employee test 1', + }, + { + id: '2', + isAssigned: false, + name: 'employee test 2', + }, + ], + }; + + // Act + const result = mapProjectFromApiToVm(project, employees); + + // Assert + expect(result).toEqual(expectedResult); + }); + }); + + describe('mapProjectFromVmToApi', () => { + it('should return empty project when feeding null', () => { + // Arrange + const project = null; + + // Act + const result = mapProjectFromVmToApi(project); + + //Assert + expect(result).toEqual(viewModel.createEmptyProject()); + }); + + it('should return empty project when feeding undefined', () => { + // Arrange + const project = undefined; + + // Act + const result = mapProjectFromVmToApi(project); + + //Assert + expect(result).toEqual(viewModel.createEmptyProject()); + }); + + it('should return expected value when feeding employee, and empty employee list', () => { + // Arrange + const project: viewModel.Project = { + id: 'test id', + name: 'test name', + isActive: true, + comments: 'Test comments', + externalId: '123', + employees: [], + }; + + const expectedValue: apiModel.Project = { + id: 'test id', + name: 'test name', + isActive: true, + comments: 'Test comments', + externalId: '123', + }; + + // Act + const result = mapProjectFromVmToApi(project); + + //Assert + expect(result).toEqual(expectedValue); + }); + + it('should return expected value when feeding project, and employee list', () => { + // Arrange + const project: viewModel.Project = { + id: 'test id', + name: 'test name', + isActive: true, + comments: 'Test comments', + externalId: '123', + employees: [ + { + id: '1', + isAssigned: true, + name: 'test name', + }, + { + id: '2', + isAssigned: false, + name: 'test name', + }, + ], + }; + + const expectedValue: apiModel.Project = { + id: 'test id', + name: 'test name', + isActive: true, + comments: 'Test comments', + externalId: '123', + }; + + // Act + const result = mapProjectFromVmToApi(project); + + //Assert + expect(result).toEqual(expectedValue); + }); + }); + + describe('mapProjectEmployeeListFromVmToApi', () => { + it('should return empty array when feeding project employee null', () => { + // Arrange + const projectEmployeeList = null; + + // Act + const result = mapProjectEmployeeListFromVmToApi(projectEmployeeList); + + // Assert + expect(result).toEqual([]); + }); + + it('should return empty array when feeding project employee undefined', () => { + // Arrange + const projectEmployeeList = undefined; + + // Act + const result = mapProjectEmployeeListFromVmToApi(projectEmployeeList); + + // Assert + expect(result).toEqual([]); + }); + + it('should return empty array when feeding project employee empty array', () => { + // Arrange + const projectEmployeeList = []; + + // Act + const result = mapProjectEmployeeListFromVmToApi(projectEmployeeList); + + // Assert + expect(result).toEqual([]); + }); + + it('should return expected result when feeding project employee list', () => { + // Arrange + const projectEmployeeList: viewModel.ProjectEmployee[] = [ + { + id: '1', + name: 'test name', + isAssigned: true, + }, + { + id: '2', + name: 'test name', + isAssigned: false, + }, + ]; + + const expectedResult: apiModel.ProjectEmployee[] = [ + { + id: '1', + isAssigned: true, + }, + { + id: '2', + isAssigned: false, + }, + ]; + + // Act + const result = mapProjectEmployeeListFromVmToApi(projectEmployeeList); + + // Assert + expect(result).toEqual(expectedResult); + }); + }); +}); diff --git a/src/pods/project/project.mappers.ts b/src/pods/project/project.mappers.ts new file mode 100644 index 0000000..1059847 --- /dev/null +++ b/src/pods/project/project.mappers.ts @@ -0,0 +1,65 @@ +import { mapToCollection } from 'common/mappers'; +import * as apiModel from './api/project.api-model'; +import * as viewModel from './project.vm'; + +const mapEmployeeSummaryFromApiToVm = ( + employee: apiModel.Employee, + projectEmployeeList: apiModel.ProjectEmployee[] +): viewModel.ProjectEmployee => { + const projectEmployee = projectEmployeeList + ? projectEmployeeList.find(pe => pe.id === employee.id) + : viewModel.createEmptyProjectEmployee(); + return { + ...employee, + isAssigned: projectEmployee?.isAssigned, + }; +}; + +const mapEmployeeSummaryListFromApiToVm = ( + employees: apiModel.Employee[], + projectEmployees: apiModel.ProjectEmployee[] +): viewModel.ProjectEmployee[] => + mapToCollection(employees, e => + mapEmployeeSummaryFromApiToVm(e, projectEmployees) + ); + +export const mapProjectFromApiToVm = ( + project: apiModel.Project, + employees: apiModel.Employee[] +): viewModel.Project => { + return Boolean(project) + ? { + ...project, + employees: mapEmployeeSummaryListFromApiToVm( + employees, + project.employees + ), + } + : viewModel.createEmptyProject(); +}; + +export const mapProjectFromVmToApi = ( + project: viewModel.Project +): apiModel.Project => { + return Boolean(project) + ? { + id: project.id, + name: project.name, + isActive: project.isActive, + externalId: project.externalId, + comments: project.comments, + } + : viewModel.createEmptyProject(); +}; + +const mapProjectEmployeeFromVmToApi = ( + projectEmployee: viewModel.ProjectEmployee +): apiModel.ProjectEmployee => ({ + id: projectEmployee.id, + isAssigned: projectEmployee.isAssigned, +}); + +export const mapProjectEmployeeListFromVmToApi = ( + projectEmployeeList: viewModel.ProjectEmployee[] +): apiModel.ProjectEmployee[] => + mapToCollection(projectEmployeeList, mapProjectEmployeeFromVmToApi); diff --git a/src/pods/project/project.vm.ts b/src/pods/project/project.vm.ts index 1ffb7ef..ea71910 100644 --- a/src/pods/project/project.vm.ts +++ b/src/pods/project/project.vm.ts @@ -4,13 +4,18 @@ export interface Project { externalId?: string; comments?: string; isActive: boolean; - employees: EmployeeSummary[]; + employees: ProjectEmployee[]; } -export interface EmployeeSummary { +export interface ProjectEmployee { id: string; isAssigned?: boolean; - employeeName: string; + name: string; +} + +export interface Report { + month: string; + year: string; } export const createEmptyProject = (): Project => ({ @@ -21,3 +26,14 @@ export const createEmptyProject = (): Project => ({ isActive: false, employees: [], }); + +export const createEmptyReport = (): Report => ({ + month: '', + year: '', +}); + +export const createEmptyProjectEmployee = (): ProjectEmployee => ({ + id: '', + name: '', + isAssigned: false, +});